#include "CrossLang.hpp"
#include <iostream>
namespace Tesses::CrossLang 
{
    
    void CrossArchiveCreate(Tesses::Framework::Filesystem::VFS* vfs,Tesses::Framework::Streams::Stream* strm,std::string name, TVMVersion version, std::string info)
    {
        std::vector<std::string> ignored_files;
        std::string file = "/.crossarchiveignore";
        if(vfs->FileExists(file))
        {
            auto strm = vfs->OpenFile(file,"rb");
            if(strm != nullptr)
            {
                Tesses::Framework::TextStreams::StreamReader reader(strm,true);
                std::string ignores;
                while(reader.ReadLine(ignores))
                {
                    if(!ignores.empty() && ignores[0] != '#')
                    {
                        ignored_files.push_back(ignores);
                    }
                    ignores.clear();
                }
            }
        }
        static std::vector<uint8_t> error_message_byte_code = {
            PUSHSTRING,
            0,
            0,
            0,
            2,
            GETVARIABLE,
            PUSHSTRING,
            0,
            0,
            0,
            3,
            PUSHSTRING,
            0,
            0,
            0,
            4,
            PUSHLONG,
            0,
            0,
            0,
            0,
            0,
            0,
            0,
            1,
            CALLMETHOD,
            RET
        };
        auto writeInt = [](Tesses::Framework::Streams::Stream* strm,uint32_t number)->void{
            uint8_t buff[4];
            BitConverter::FromUint32BE(buff[0],number);
            strm->WriteBlock(buff,4);
        };
        auto writeStr = [&writeInt](Tesses::Framework::Streams::Stream* strm,std::string text)->void {
            writeInt(strm,(uint32_t)text.size());
            strm->WriteBlock((const uint8_t*)text.c_str(),text.size());
        };

        std::vector<std::string> strs;
        strs.push_back(name);
        strs.push_back(info);
        strs.push_back("Console");
        strs.push_back("WriteLine");
        strs.push_back("You are trying to run a crvm archive, stop it you won't get anywhere!\nUse crossarchiveextract instead.");

        std::vector<std::string> resources;

        auto ensureResource = [&resources](std::string path)->uint32_t {
            for(uint32_t i = 0; i < (uint32_t)resources.size(); i++)
            {
                if(resources[i] == path) return i;
            }
            uint32_t index = (uint32_t)resources.size();
            resources.push_back(path);
            return index;
        };
        auto ensureString = [&strs](std::string str)->uint32_t {
            for(uint32_t i = 0; i < (uint32_t)strs.size(); i++)
            {
                if(strs[i] == str) return i;
            }
            uint32_t index = (uint32_t)strs.size();
            strs.push_back(str);
            return index;
        };
        Tesses::Framework::Streams::MemoryStream ms(true);

        std::function<void(Tesses::Framework::Filesystem::VFSPath)> walkFS = [&ignored_files,vfs,&ensureString,&ensureResource,&ms,&walkFS,&writeInt](Tesses::Framework::Filesystem::VFSPath path)->void {
           
            if(vfs->DirectoryExists(path))
            {

                ms.WriteByte(1);
                std::vector<std::string> paths;
                for(auto item : vfs->EnumeratePaths(path))
                {
                    if(!item.relative && item.path.size() == 1 && item.path[0] == "__resdir_tmp") continue;
                    if(!item.relative && item.path.size() == 1 && item.path[0] == ".crossarchiveignore") continue;
                    std::string filename = item.GetFileName();
                    bool shallIgnore=false;
                    for(auto ign : ignored_files) if(ign == filename) {shallIgnore=true; break;}
                    if(shallIgnore) continue;
                    if(vfs->DirectoryExists(item) || vfs->RegularFileExists(item))
                        paths.push_back(item.GetFileName());
                }
                writeInt(&ms,(uint32_t)paths.size());
                for(auto item : paths)
                {
                    writeInt(&ms,ensureString(item));
                    walkFS(path / item);
                }
            }
            else if(vfs->RegularFileExists(path))
            {
                ms.WriteByte(0);
                writeInt(&ms,ensureResource(path.ToString()));
            }
        };

        walkFS(std::string("/"));

        uint8_t main_header[18];
        memcpy(main_header,"TCROSSVM",8);
        TVMVersion rtVersion(TVM_MAJOR,TVM_MINOR,TVM_PATCH,TVM_BUILD,TVM_VERSIONSTAGE);
        rtVersion.ToArray(main_header+8);
        version.ToArray(main_header+13);
        strm->WriteBlock(main_header,sizeof(main_header));
        writeInt(strm,(uint32_t)(5+resources.size()));
        strm->WriteBlock((const uint8_t*)"STRS",4);
        uint32_t sz=4;
        for(auto str : strs)
            sz += (uint32_t)(4 + str.size());
        writeInt(strm,sz);
        writeInt(strm,(uint32_t)strs.size());
        for(auto str : strs)
        {
            writeStr(strm,str);
        }
        strm->WriteBlock((const uint8_t*)"NAME",4);
        writeInt(strm,4);
        writeInt(strm,0);
        strm->WriteBlock((const uint8_t*)"INFO",4);
        writeInt(strm,4);
        writeInt(strm,1);
        strm->WriteBlock((const uint8_t*)"CHKS",4);
        writeInt(strm,(uint32_t)(12+error_message_byte_code.size()));
        writeInt(strm,1);
        writeInt(strm,0);
        writeInt(strm,(uint32_t)error_message_byte_code.size());
        strm->WriteBlock(error_message_byte_code.data(),error_message_byte_code.size());
        


        for(auto res : resources)
        {
            strm->WriteBlock((const uint8_t*)"RESO",4);
            auto strm2 = vfs->OpenFile(res,"rb");
            writeInt(strm,(uint32_t)strm2->GetLength());
            strm2->CopyTo(strm);
            delete strm2;
        }
        strm->WriteBlock((const uint8_t*)"ARCV",4);
        writeInt(strm,(uint32_t)ms.GetLength());
        ms.Seek(0,Tesses::Framework::Streams::SeekOrigin::Begin);
        ms.CopyTo(strm);
    }
    std::pair<std::pair<std::string,TVMVersion>,std::string> CrossArchiveExtract(Tesses::Framework::Streams::Stream* strm,Tesses::Framework::Filesystem::VFS* vfs)
    {
        auto ensure = [strm](uint8_t* buffer,size_t count)->void{

            size_t read = strm->ReadBlock(buffer, count);
            if(read < count) throw VMException("End of file, could not read " + std::to_string((int64_t)count) + " byte(s)., offset=" + std::to_string(strm->GetLength()));
        };
        auto ensureInt = [&ensure]()->uint32_t {
            uint8_t buffer[4];
            ensure(buffer,4);
            return BitConverter::ToUint32BE(buffer[0]);
        };
        auto ensureStr = [&ensure,&ensureInt]()-> std::string {
            auto len = ensureInt();
            if(len == 0) return {};
            std::string str={};
            str.resize((size_t)len);
            ensure((uint8_t*)str.data(),str.size());
            return str;
        };
        std::vector<std::string> strs;
        auto getStr = [&ensure,&ensureInt,&strs]()-> std::string {
            auto index = ensureInt();
            if(index > strs.size()) throw VMException("String does not exist in TCrossVM file, expected string index: " + std::to_string(index) + ", total strings: " + std::to_string(strs.size()));
            return strs[index];
        };
        uint8_t main_header[18];
        ensure(main_header,sizeof(main_header));
        if(strncmp((const char*)main_header,"TCROSSVM",8) != 0) throw VMException("Invalid TCrossVM image.");
        TVMVersion version(main_header+8);
        if(version.CompareToRuntime() == 1)
        {
            throw VMException("Runtime is too old.");
        }
        TVMVersion v2(main_header+13);
        std::string name;
        std::string info;
        

        size_t _len = (size_t)ensureInt();

        char table_name[4];

        uint32_t resource_id = 0;

         Tesses::Framework::Filesystem::VFSPath tmpDir("/__resdir_tmp");

        vfs->CreateDirectory(tmpDir);
        for(size_t i = 0;i < _len; i++)
        {
            ensure((uint8_t*)table_name,sizeof(table_name));
            size_t tableLen = (size_t)ensureInt();
            if(strncmp(table_name,"NAME",4) == 0) 
            {
                name = getStr();
            }
            else if(strncmp(table_name,"INFO",4) == 0) 
            {
                info = getStr();

            }
            
            else if(strncmp(table_name,"RESO",4) == 0) //resources (using embed)
            {
                
                auto path = tmpDir /std::to_string(resource_id);    

                auto strm2 = vfs->OpenFile(path,"wb");
                size_t read = 0;
                size_t offset = 0;
                uint8_t buff[1024];
                do {
                    read = std::min(std::min(tableLen-offset,tableLen), sizeof(buff));
                    read = strm->Read(buff,read);
                    if(read > 0)
                        strm2->WriteBlock(buff,read);
                    offset+=read;
                } while(read > 0);
                delete strm2;
                resource_id++;
            }

            else if(strncmp(table_name,"STRS",4) == 0) //strings
            {
                size_t strsLen = (size_t)ensureInt();
                for(size_t j = 0;j < strsLen;j++)
                {
                    strs.push_back(ensureStr());
                }
            }
            else if(strncmp(table_name,"ARCV",4) == 0)
            {
                size_t offset = 0;

                std::function<void(Tesses::Framework::Filesystem::VFSPath)> walkEntry = [strm,vfs,&getStr,&ensureInt,&tmpDir,&tableLen,&offset,&walkEntry](Tesses::Framework::Filesystem::VFSPath path)->void {
                    if(offset + 1 > tableLen) return;
                    uint8_t type = strm->ReadByte();
                    offset++;
                    if(type == 1)
                    {
                        //ISDIR
                        vfs->CreateDirectory(path);
                        if(offset + 4 > tableLen) return;
                        uint32_t count = ensureInt();
                        offset +=4;
                        for(uint32_t i = 0; i < count; i++)
                        {
                            if(offset + 4 > tableLen) return;
                            std::string name = getStr();
                            offset +=4;
                            walkEntry(path / name);
                        }
                    } 
                    else if(type == 0)
                    {
                        if(offset + 4 > tableLen) return;
                        uint32_t index = ensureInt();
                        auto fSrc = tmpDir /std::to_string(index);    
                        vfs->MoveFile(fSrc, path);
                    }
                };

                walkEntry(std::string("/"));
            }
            else
            {
                if(strm->CanSeek())
                {
                    strm->Seek((int64_t)tableLen,Tesses::Framework::Streams::SeekOrigin::Current);
                }
                else{
                    uint8_t* buffer=new uint8_t[tableLen];
                    ensure(buffer,tableLen);
                    delete[] buffer;
                }
            }
        }
        vfs->DeleteDirectoryRecurse(tmpDir);
        
        return std::pair<std::pair<std::string,TVMVersion>,std::string>(std::pair<std::string,TVMVersion>(name,v2),info);
    }
}
