/////////////////////////////////////////////////////////////////////////////
// Name:        src/common/fs_mem.cpp
// Purpose:     in-memory file system
// Author:      Vaclav Slavik
// Copyright:   (c) 2000 Vaclav Slavik
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#include "wx/wxprec.h"


#if wxUSE_FILESYSTEM && wxUSE_STREAMS

#include "wx/fs_mem.h"

#ifndef WX_PRECOMP
    #include "wx/intl.h"
    #include "wx/log.h"
    #include "wx/wxcrtvararg.h"
    #if wxUSE_GUI
        #include "wx/image.h"
    #endif // wxUSE_GUI
#endif

#include "wx/mstream.h"

// represents a file entry in wxMemoryFS
class wxMemoryFSFile
{
public:
    wxMemoryFSFile(const void *data, size_t len, const wxString& mime)
    {
        m_Data = new char[len];
        memcpy(m_Data, data, len);
        m_Len = len;
        m_MimeType = mime;
        InitTime();
    }

    wxMemoryFSFile(const wxMemoryOutputStream& stream, const wxString& mime)
    {
        m_Len = stream.GetSize();
        m_Data = new char[m_Len];
        stream.CopyTo(m_Data, m_Len);
        m_MimeType = mime;
        InitTime();
    }

    virtual ~wxMemoryFSFile()
    {
        delete[] m_Data;
    }

    char *m_Data;
    size_t m_Len;
    wxString m_MimeType;
#if wxUSE_DATETIME
    wxDateTime m_Time;
#endif // wxUSE_DATETIME

private:
    void InitTime()
    {
#if wxUSE_DATETIME
        m_Time = wxDateTime::Now();
#endif // wxUSE_DATETIME
    }

    wxDECLARE_NO_COPY_CLASS(wxMemoryFSFile);
};

#if wxUSE_BASE


//--------------------------------------------------------------------------------
// wxMemoryFSHandler
//--------------------------------------------------------------------------------


wxMemoryFSHandlerBase::wxMemoryFSHash wxMemoryFSHandlerBase::m_Hash;


wxMemoryFSHandlerBase::wxMemoryFSHandlerBase() : wxFileSystemHandler()
{
}

wxMemoryFSHandlerBase::~wxMemoryFSHandlerBase()
{
    // as only one copy of FS handler is supposed to exist, we may silently
    // delete static data here. (There is no way how to remove FS handler from
    // wxFileSystem other than releasing _all_ handlers.)
}

bool wxMemoryFSHandlerBase::CanOpen(const wxString& location)
{
    return GetProtocol(location) == "memory";
}

wxFSFile * wxMemoryFSHandlerBase::OpenFile(wxFileSystem& WXUNUSED(fs),
                                           const wxString& location)
{
    wxMemoryFSHash::const_iterator i = m_Hash.find(GetRightLocation(location));
    if ( i == m_Hash.end() )
        return nullptr;

    const auto& obj = i->second;

    return new wxFSFile
               (
                    new wxMemoryInputStream(obj->m_Data, obj->m_Len),
                    location,
                    obj->m_MimeType,
                    GetAnchor(location)
#if wxUSE_DATETIME
                    , obj->m_Time
#endif // wxUSE_DATETIME
               );
}

wxString wxMemoryFSHandlerBase::FindFirst(const wxString& url, int flags)
{
    // Make sure to reset the find iterator, so that calling FindNext() doesn't
    // reuse its value from the last search that could well be invalid.
    m_findIter = m_Hash.end();

    if ( (flags & wxDIR) && !(flags & wxFILE) )
    {
        // we only store files, not directories, so we don't risk finding
        // anything
        return wxString();
    }

    const wxString spec = GetRightLocation(url);
    if ( spec.find_first_of("?*") == wxString::npos )
    {
        // simple case: there are no wildcard characters so we can return
        // either 0 or 1 results and we can find the potential match quickly
        return m_Hash.count(spec) ? url : wxString();
    }
    //else: deal with wildcards in FindNext()

    m_findArgument = spec;
    m_findIter = m_Hash.begin();

    return FindNext();
}

wxString wxMemoryFSHandlerBase::FindNext()
{
    while ( m_findIter != m_Hash.end() )
    {
        const wxString& path = m_findIter->first;

        // advance m_findIter first as we need to do it anyhow, whether it
        // matches or not
        ++m_findIter;

        if ( path.Matches(m_findArgument) )
            return "memory:" + path;
    }

    return wxString();
}

bool wxMemoryFSHandlerBase::CheckDoesntExist(const wxString& filename)
{
    if ( m_Hash.count(filename) )
    {
        wxLogError(_("Memory VFS already contains file '%s'!"), filename);
        return false;
    }

    return true;
}


/*static*/
void wxMemoryFSHandlerBase::DoAddFile(const wxString& filename,
                                      wxMemoryFSFile* file)
{
    m_Hash[filename] = std::unique_ptr<wxMemoryFSFile>(file);
}

/*static*/
void wxMemoryFSHandlerBase::AddFileWithMimeType(const wxString& filename,
                                                const wxString& textdata,
                                                const wxString& mimetype)
{
    // We try to use the provided data "as is" if possible, but if not, we fall
    // back to UTF-8 because it's better to do this than just lose the data
    // completely.
    wxCharBuffer buf(textdata.To8BitData());
    if ( !buf.length() )
        buf = textdata.utf8_str();

    AddFileWithMimeType(filename, buf.data(), buf.length(), mimetype);
}


/*static*/
void wxMemoryFSHandlerBase::AddFileWithMimeType(const wxString& filename,
                                                const void *binarydata, size_t size,
                                                const wxString& mimetype)
{
    if ( !CheckDoesntExist(filename) )
        return;

    DoAddFile(filename, new wxMemoryFSFile(binarydata, size, mimetype));
}

/*static*/
void wxMemoryFSHandlerBase::AddFile(const wxString& filename,
                                    const wxString& textdata)
{
    AddFileWithMimeType(filename, textdata, wxEmptyString);
}


/*static*/
void wxMemoryFSHandlerBase::AddFile(const wxString& filename,
                                    const void *binarydata, size_t size)
{
    AddFileWithMimeType(filename, binarydata, size, wxEmptyString);
}



/*static*/ void wxMemoryFSHandlerBase::RemoveFile(const wxString& filename)
{
    if ( !m_Hash.erase(filename) )
    {
        wxLogError(_("Trying to remove file '%s' from memory VFS, "
                     "but it is not loaded!"),
                   filename);
    }
}

#endif // wxUSE_BASE

#if wxUSE_GUI

#if wxUSE_IMAGE
/*static*/ void
wxMemoryFSHandler::AddFile(const wxString& filename,
                           const wxImage& image,
                           wxBitmapType type)
{
    if ( !CheckDoesntExist(filename) )
        return;

    wxMemoryOutputStream mems;
    if ( image.IsOk() && image.SaveFile(mems, type) )
    {
        DoAddFile(filename, new wxMemoryFSFile
                                (
                                    mems,
                                    wxImage::FindHandler(type)->GetMimeType()
                                ));
    }
    else
    {
        wxLogError(_("Failed to store image '%s' to memory VFS!"), filename);
    }
}

/*static*/ void
wxMemoryFSHandler::AddFile(const wxString& filename,
                           const wxBitmap& bitmap,
                           wxBitmapType type)
{
    wxImage img = bitmap.ConvertToImage();
    AddFile(filename, img, type);
}

#endif // wxUSE_IMAGE

#endif // wxUSE_GUI


#endif // wxUSE_FILESYSTEM && wxUSE_FS_ZIP
