#include <string.h>
#include <map>
#include "../minizip/ioapi_mem.h"
#include "core/ZipFileSystem.h"
#include "core/Object.h"
#include "utils/stringUtils.h"
#include <algorithm>
//#include "utils/utils.h"

#ifdef __S3E__
#include "s3eFile.h"
#endif

using namespace oxygine;
namespace oxygine
{
    namespace file
    {
        bool sortFiles(const file_entry& ob1, const file_entry& ob2)
        {
            return strcmp_cns(ob1.name, ob2.name) < 0;
        }

        bool readEntry(const file_entry* entry, file::buffer& bf)
        {
            bf.data.clear();
            int r = unzGoToFilePos(entry->zp, const_cast<unz_file_pos*>(&entry->pos));
            OX_ASSERT(r == UNZ_OK);

            unz_file_info file_info;
            r = unzGetCurrentFileInfo(entry->zp, &file_info, 0, 0, 0, 0, 0, 0);
            OX_ASSERT(r == UNZ_OK);

            unzOpenCurrentFile(entry->zp);

            bf.data.resize(file_info.uncompressed_size);
            r = unzReadCurrentFile(entry->zp, &bf.data.front(), (int)bf.data.size());
            OX_ASSERT(r == (int)file_info.uncompressed_size);

            unzCloseCurrentFile(entry->zp);
            return true;
        }

        Zips::Zips(): _sort(false)
        {

        }

        Zips::~Zips()
        {
            reset();
        }

        void Zips::read(unzFile zp)
        {
            do
            {
                unz_file_pos pos;
                unzGetFilePos(zp, &pos);

                file_entry entry;
                unzGetCurrentFileInfo(zp, 0, entry.name, sizeof(entry.name) - 1, 0, 0, 0, 0);
                entry.pos = pos;
                entry.zp = zp;

                //strcpy(entry.name, entry.name);

                _files.push_back(entry);

            }
            while (unzGoToNextFile(zp) != UNZ_END_OF_LIST_OF_FILE);

            _sort = true;
        }

        void Zips::add(const unsigned char* data, unsigned int size)
        {
            zlib_filefunc_def ff;
            fill_memory_filefunc(&ff);

            zmemdata dta;
            dta.data = (char*)data;
            dta.size = size;

            unzFile zp = unzOpen2((const char*)&dta, &ff);
            OX_ASSERT(zp);
            if (!zp)
                return;


            zpitem item;
            item.handle = zp;
            _zps.push_back(item);

            read(zp);
        }

        void Zips::add(std::vector<char>& data)
        {
            zlib_filefunc_def ff;
            fill_memory_filefunc(&ff);

            zmemdata dta;
            dta.data = (char*)&data.front();
            dta.size = data.size();

            unzFile zp = unzOpen2((const char*)&dta, &ff);
            OX_ASSERT(zp);
            if (!zp)
                return;

            _zps.push_back(zpitem());
            zpitem& item = _zps.back();
            item.handle = zp;
            std::swap(item.data, data);

            read(zp);
        }

        voidpf ZCALLBACK ox_fopen(voidpf opaque, const char* filename, int mode)
        {
            return file::open(filename, "rb");
        }

        uLong ZCALLBACK ox_fread(voidpf opaque, voidpf stream, void* buf, uLong size)
        {
            return file::read((handle)stream, buf, (unsigned int)size);
        }

        /*
        uLong ZCALLBACK ox_fwriteOF(voidpf opaque, voidpf stream, const void* buf, uLong size)
        {

        }
        */

        long ZCALLBACK ox_ftell(voidpf opaque, voidpf stream)
        {
            return file::tell((handle)stream);
        }

        long ZCALLBACK ox_fseek(voidpf opaque, voidpf stream, uLong offset, int origin)
        {
            file::seek((handle)stream, (unsigned int)offset, origin);
            return 0;
        }

        int ZCALLBACK ox_fclose(voidpf opaque, voidpf stream)
        {
            file::close((handle)stream);
            return 0;
        }

        int ZCALLBACK ox_ferror(voidpf opaque, voidpf stream)
        {
            return 0;
        }



        void Zips::add(const char* name)
        {
            zlib_filefunc_def zpfs;
            memset(&zpfs, 0, sizeof(zpfs));

            zpfs.zopen_file = ox_fopen;
            zpfs.zread_file = ox_fread;
            zpfs.zwrite_file = 0;
            zpfs.ztell_file = ox_ftell;
            zpfs.zseek_file = ox_fseek;
            zpfs.zclose_file = ox_fclose;
            zpfs.zerror_file = ox_ferror;

            unzFile zp = unzOpen2(name, &zpfs);//todo, optimize search in zip
            OX_ASSERT(zp);
            if (!zp)
                return;

            zpitem item;
            item.handle = zp;
            _zps.push_back(item);

            read(zp);
        }

        void Zips::update()
        {
            std::sort(_files.begin(), _files.end(), sortFiles);
#ifdef OX_DEBUG
            for (size_t i = 0; i < _files.size() - 1; ++i)
            {
                OX_ASSERT(strcmp(_files[i].name, _files[i + 1].name) != 0);
            }
#endif
            log::messageln("ZipFS, total files: %d", _files.size());
        }

        bool Zips::read(const char* name, file::buffer& bf)
        {
            const file_entry* entry = getEntryByName(name);
            if (!entry)
                return false;
            return readEntry(entry, bf);
        }

        bool Zips::read(const file_entry* entry, file::buffer& bf)
        {
            return readEntry(entry, bf);
        }

        void Zips::reset()
        {
            for (zips::iterator i = _zps.begin(); i != _zps.end(); ++i)
            {
                zpitem& f = *i;
                unzClose(f.handle);
            }

            _zps.clear();
            _files.clear();
        }


        bool findEntry(const file_entry& g, const char* name)
        {
            return strcmp_cns(g.name, name) < 0;
        }

        const file_entry* Zips::getEntryByName(const char* name)
        {
            if (_sort)
            {
                update();
                _sort = false;
            }

            files::const_iterator it = std::lower_bound(_files.begin(), _files.end(), name, findEntry);
            if (it != _files.end())
            {
                const file_entry& g = *it;
                if (!strcmp_cns(g.name, name))
                    return &g;
            }

            return 0;
        }

        const file_entry* Zips::getEntry(int index)
        {
            if (_sort)
            {
                update();
                _sort = false;
            }

            return &_files[index];
        }

        size_t Zips::getNumEntries() const
        {
            return _files.size();
        }

        class fileHandleZip: public fileHandle
        {
        public:

            fileHandleZip(const file_entry* entry): _entry(entry)
            {
                int r = 0;
                r = unzGoToFilePos(entry->zp, const_cast<unz_file_pos*>(&entry->pos));
                OX_ASSERT(r == UNZ_OK);
                r = unzOpenCurrentFile(entry->zp);
                OX_ASSERT(r == UNZ_OK);
            }

            void release()
            {
                int r = unzCloseCurrentFile(_entry->zp);
                OX_ASSERT(r == UNZ_OK);
                delete this;
            }

            int seek(unsigned int offset, int whence)
            {
                OX_ASSERT(!"not implemented");
                return 0;
            }

            unsigned int tell() const
            {
                OX_ASSERT(!"not implemented");
                return 0;
            }

            unsigned int read(void* dest, unsigned int size)
            {
                return unzReadCurrentFile(_entry->zp, dest, size);
            }

            unsigned int write(const void* src, unsigned int size)
            {
                return 0;
            }

            unsigned int getSize() const
            {
                unz_file_info file_info;
                unzGetCurrentFileInfo(_entry->zp, &file_info, 0, 0, 0, 0, 0, 0);
                return file_info.uncompressed_size;
            }

            const file_entry* _entry;

        };


        void ZipFileSystem::add(const char* zip)
        {
            log::messageln("ZipFileSystem::add %s", zip);
            _zips.add(zip);
        }

        void ZipFileSystem::add(const unsigned char* data, unsigned int size)
        {
            _zips.add(data, size);
        }

        void ZipFileSystem::add(std::vector<char>& data)
        {
            _zips.add(data);
        }

        void ZipFileSystem::reset()
        {
            _zips.reset();
        }

        FileSystem::status ZipFileSystem::_open(const char* file, const char* mode, error_policy ep, file::fileHandle*& fh)
        {
            const file_entry* entry = _zips.getEntryByName(file);
            if (entry)
            {
                fh = new fileHandleZip(entry);
                return status_ok;
            }

            handleErrorPolicy(ep, "can't find zip file entry: %s", file);
            return status_error;
        }

        FileSystem::status ZipFileSystem::_deleteFile(const char* file)
        {
            return status_error;
        }

        FileSystem::status ZipFileSystem::_makeDirectory(const char* file)
        {
            return status_error;
        }

        FileSystem::status ZipFileSystem::_deleteDirectory(const char* file)
        {
            return status_error;
        }

        FileSystem::status ZipFileSystem::_renameFile(const char* src, const char* dest)
        {
            return status_error;
        }
    }
}