#include "scriptfile.hh"
#include "compress.hh"
#include "strload.hh"
#include "symbols.hh"
#include "msgdump.hh"
#include "rommap.hh"
#include "romaddr.hh"
#include "config.hh"

#include "dumptext.hh"

#include <cstdarg>

namespace
{
    std::u32string dict_converted[256];
    ctstring dict_unconverted[256];
}

void LoadDict(unsigned offs, unsigned len)
{
    vector<ctstring> strings = LoadPStrings(offs, len, U"dictionary(p)");

    for(unsigned a=0; a<strings.size(); ++a)
    {
        const ctstring &s = strings[a];

        dict_unconverted[a + 0x21] = s;

        std::u32string tmp(s.size(), 0);
        for(unsigned b=0; b<s.size(); ++b)
            tmp[b] = getchar32_t(s[b], cset_12pix);
        dict_converted[a + 0x21] = tmp;
    }
}

void DumpDict()
{
    StartBlock(U"d", U"dictionary");

    for(unsigned a=0; a<256; ++a)
    {
        const std::u32string &s = dict_converted[a];
        if(s.empty()) continue;

        PutBase16Label(a);
        PutContent(s + U';', false);
    }

    EndBlock();
}

const std::u32string Disp8Char(ctchar k)
{
    const Symbols::revtype &symbols = Symbols.GetRev(2);

    Symbols::revtype::const_iterator i = symbols.find(k);

    if(i != symbols.end())
        return i->second;

    if(k == 0x2D) return U":";

    char32_t tmp = getchar32_t(k, cset_8pix);
    if(tmp == ilseq)
    {
        return wformat(U"[%02X]", k);
    }

    std::u32string result;
    result += tmp;
    return result;
}

const std::u32string Disp12Char(ctchar k)
{
    // Override these special ones to get proper formatting:
    switch(k)
    {
        /*
            [nl] is a linefeed.
            [pausenl] is a pause, then linefeed.
            [cls] is a screen clearing.
            [pause] is a pause, then clear screen.
        */

        case 0x05: return U"[nl]\n";
        case 0x06: return U"[nl]\n   ";
        case 0x07: return U"[pausenl]\n";
        case 0x08: return U"[pausenl]\n   ";
        case 0x09: return U"\n[cls]\n";
        case 0x0A: return U"\n[cls]\n   ";
        case 0x0B: return U"\n[pause]\n";
        case 0x0C: return U"\n[pause]\n   ";
    }

    const Symbols::revtype &symbols = Symbols.GetRev(16);

    Symbols::revtype::const_iterator i = symbols.find(k);

    if(i != symbols.end())
        return i->second;

    // Note:
    // The character names
    // (Crono,Marle,Lucca,Robo,Frog,Ayla,Magus,Nadia,Epoch)
    // are quite obfuscated in the ROM. We use hardcoded
    // symbol map instead of trying to decipher the ROM.

    if(k < 256 && !dict_converted[k].empty())
        return dict_converted[k];

    char32_t tmp = getchar32_t(k, cset_12pix);
    if(tmp == ilseq)
    {
        return wformat(U"[%02X]", k);
    }

    std::u32string result;
    result += tmp;
    return result;
}

namespace
{
    const ctstring AttemptUnwrapParagraph(const ctstring& para)
    {
        const ctchar space  = getctchar(' ');
        const ctchar colon  = getctchar(':');
        const ctchar period = getctchar('.');
        const ctchar que    = getctchar('?');
        const ctchar excl   = getctchar('!');
        const ctchar lquo   = getctchar((unsigned char)'«');
        const ctchar rquo   = getctchar((unsigned char)'»');

        ctstring space3(3, space);

        bool line_firstword = true;
        bool first_line     = true;
        bool indented       = para.substr(0, 3) == space3;
        bool had_delimiter  = false;

        const Symbols::revtype &symbols = Symbols.GetRev(16);

        ctstring result = para;
        for(unsigned a=0; a<result.size(); ++a)
        {
            ctchar k = result[a];
            if(k < 256 && !dict_unconverted[k].empty())
            {
                result.replace(a, 1, dict_unconverted[k]);
                k = result[a];
            }

            if(k == 0x05 /* nl */
            || k == 0x07 /* pausenl */)
            {
                /* FIXME: This is not a good check for $6q2o and similar */
                if(result[a+1] == lquo)
                {
                    had_delimiter = true;
                }

                if(result.find(space3 + space3, a+1) != result.npos)
                {
                    /* Abort wrapping - there's preformatted text following. */
                    return result;
                }

                if(!had_delimiter && k != 0x07 && !line_firstword)
                {
                    if(indented && result.substr(a+1, 3) == space3)
                    {
                        result.replace(a, 4, 1, space);
                        continue;
                    }
                    if(!indented && result.substr(a+1, 3) != space3)
                    {
                        result.replace(a, 1, 1, space);
                        continue;
                    }
                }
                line_firstword = true;
                first_line = false;
                indented = result.substr(a+1, 3) == space3;
                continue;
            }

            had_delimiter =
                k == colon || k == period
             || k == que || k == excl
                 /* FIXME: Not a good check against "..." */
             || (k >= 0x21 && symbols.find(k) != symbols.end())
                 /* FIXME: Not a good check against $8SFk and similar */
             || k == 0x0D || k == 0x0E || k == 0x0F
             || k == rquo
                 ;

            if(k == colon && line_firstword)
                indented = true;

            if(k == space || k == colon)
                line_firstword = false;
        }
        return result;
    }

    void AttemptUnwrap(ctstring& line)
    {
        const bool Attempt = GetConf("dumper", "attempt_unwrap");
        if(!Attempt) return;

        ctstring space3(3, getctchar(' '));

        /* Replace all [nl3] with [nl] + 3 spaces and so on */
        for(std::size_t a=0; a<line.size(); ++a)
        {
            switch(line[a])
            {
                case 0x06: line[a] = 0x05; break;
                case 0x08: line[a] = 0x07; break;
                case 0x0A: line[a] = 0x09; break;
                case 0x0C: line[a] = 0x0B; break;
                default: continue;
            }
            line.insert(a+1, space3);
            a += space3.size();
        }

        ctstring result;
        unsigned parabegin = 0;
        for(unsigned a=0; a<=line.size(); ++a)
        {
            if(a == line.size()
            || line[a] == 0x09 /* cls */
            || line[a] == 0x0B /* pause */)
            {
                result += AttemptUnwrapParagraph(line.substr(parabegin, a-parabegin));
                if(a < line.size()) result += line[a];
                parabegin = a+1;
            }
        }
        line = result;
    }

    const std::u32string Get12string(const ctstring& value, bool dolf)
    {
        ctstring s = value;
        std::u32string result;

        if(dolf) AttemptUnwrap(s);

        for(unsigned b=0; b<s.size(); ++b)
        {
            switch(s[b])
            {
                case 0x03:
                {
                    result += wformat(U"[delay %02X]", (unsigned char)s[++b]);
                    break;
                }
                case 0x12:
                {
                    ++b;
                    if(s[b] == 0x00) result += U"[tech]";
                    else if(s[b] == 0x01) result += U"[monster]";
                    else
                    {
                        result += wformat(U"[12][%02X]", (unsigned char)s[b]);
                    }
                    break;
                }
                default:
                {
                    result += Disp12Char(s[b]);
                }
            }
        }
        return result;
    }

    const std::u32string Get8string(const ctstring& value)
    {
        ctstring s = value;
        std::u32string result;

        unsigned attr=0;
        for(unsigned b=0; b<s.size(); ++b)
        {

Retry:
            switch(s[b])
            {
#if 1
                case 1:
                {
                    result += U"[next]";
                    break;
                }
                case 2:
                {
                    unsigned c;
                    c = (unsigned char)s[b+1] + 256*(unsigned char)s[b+2]; b+=2;
                    result += wformat(U"[goto,%04X]", c);
                    break;
                }
                case 3:
                {
                    unsigned c1, c2;
                    c1 = (unsigned char)s[b+1] + 256*(unsigned char)s[b+2]; b+=2;
                    c2 = (unsigned char)s[b+1] + 256*(unsigned char)s[b+2]; b+=2;
                    result += wformat( U"[func1,%04X,%04X]", c1,c2);
                    break;
                }
                case 4:
                {
                    unsigned c1, c2;
                    c2 = (unsigned char)s[b+1] + 256*(unsigned char)s[b+2]; b+=2;
                    c1 = (unsigned char)s[++b];

#if 1
                    if(c1 >= 0x40 && (c1 < 0x7E || c1 >= 0x80))
                    {
                        unsigned addr = (SNES2ROMpage(c1) << 16) + c2;

                        unsigned bytes;
                        ctstring str = LoadZString(addr, bytes, U"substring", Extras_8);

                        //result += U'{'; //}

                        b -= 3;
                        s.erase(b, 4);
                        s.insert(b, str);
                        goto Retry;
                    }
#endif

                    result += wformat(U"[substr,%02X%04X]", c1,c2);
                    break;
                }
                case 5:
                {
                    unsigned c;
                    c = (unsigned char)s[b+1] + 256*(unsigned char)s[b+2]; b+=2;
                    result += wformat(U"[member,%04X]", c);
                    break;
                }
                case 6:
                {
                    unsigned c;
                    c = (unsigned char)s[b+1] + 256*(unsigned char)s[b+2]; b+=2;
                    result += wformat(U"[attrs,%04X]", c);
                    break;
                }
                case 7:
                {
                    unsigned c;
                    c = (unsigned char)s[b+1] + 256*(unsigned char)s[b+2]; b+=2;
                    result += wformat(U"[out,%04X]", c);
                    break;
                }
                case 8:
                {
                    unsigned c = (unsigned char)s[++b];
                    result += wformat(U"[spc,%02X]", c);
                    break;
                }
                case 9:
                {
                    unsigned c = (unsigned char)s[++b];
                    result += wformat(U"[len,%02X]", c);
                    break;
                }
                case 10:
                {
                    unsigned c = (unsigned char)s[++b];
                    attr = c;
                    result += wformat(U"[attr,%02X]", c);
                    break;
                }
                case 11:
                {
                    unsigned c1, c2;
                    c1 = (unsigned char)s[b+1] + 256*(unsigned char)s[b+2]; b+=2;
                    c2 = (unsigned char)s[b+1] + 256*(unsigned char)s[b+2]; b+=2;
                    result += wformat(U"[func2,%04X,%04X]", c1,c2);
                    break;
                }
                case 12:
                {
                    unsigned c1, c2;
                    c1 = (unsigned char)s[++b];
                    c2 = (unsigned char)s[b+1] + 256*(unsigned char)s[b+2]; b+=2;
                    result += wformat(U"[stat,%02X,%04X]", c1,c2);
                    break;
                }
#endif
                default:
                {
#if 1
                    if(attr & 0x03)
                    {
                        result += U"[gfx";
                        while(b < s.size())
                        {
                            unsigned byte = s[b];
                            if(byte <= 12) { --b; break; }

                            result += wformat(U",%02X", byte);
                            ++b;
                        }
                        result += U']';
                    }
                    else
                    {
                        result += (Disp8Char(s[b]));
                    }
#else
                    result += wformat(U"[%02X]", s[b]);
#endif
                }
            }
        }
        return result;
    }
}

void DumpZStrings(const unsigned offs,
                  const std::u32string& what,
                  unsigned len,
                  bool dolf)
{
    MessageBeginDumpingStrings(offs);

    const std::u32string what_tab = what + U"(z)";

    vector<ctstring> strings = LoadZStrings(offs, len, what_tab, Extras_12);

    StartBlock(U"z", what);

    for(unsigned a=0; a<strings.size(); ++a)
    {
        std::u32string line = Get12string(strings[a], dolf);

        PutBase62Label(offs + a*2);

        PutContent(line, dolf);
    }

    EndBlock();
    MessageDone();
}

void DumpMZStrings(const unsigned offs,
                   const std::u32string& what,
                   unsigned len,
                   bool dolf)
{
    MessageBeginDumpingStrings(offs);

    const std::u32string what_tab = what + U"(Z)";

    vector<ctstring> strings = LoadZStrings(offs, len, what_tab, Extras_12);

    StartBlock(U"z", what + U" (change this to *Z to allow free relocation)");

    for(unsigned a=0; a<strings.size(); ++a)
    {
        std::u32string line = Get12string(strings[a], dolf);
        PutBase62Label(offs + a*2);
        PutContent(line, dolf);
    }

    EndBlock();
    MessageDone();
}

void DumpRZStrings(const std::u32string& what,
                   unsigned len,
                   bool dolf,
                   ...)
{
    unsigned pageaddr = 0;
    unsigned offsaddr = 0;

    std::u32string label = U"z";

    va_list ap;
    va_start(ap, dolf);

    for(;;)
    {
        char format = static_cast<char> (va_arg(ap, int));
        if(!format) break;
        label += U':';
        label += AscToWchar(format);
        unsigned addr = va_arg(ap, unsigned);
        label += Base62Label(addr);
        if(format == '^') pageaddr = addr;
        if(format == '!') offsaddr = addr;
    }
    va_end(ap);

    unsigned offs = (ROM[pageaddr  ] << 16)
                  | (ROM[offsaddr+1] << 8)
                  | (ROM[offsaddr  ]);
    offs = SNES2ROMaddr(offs);

    MessageBeginDumpingStrings(offs);

    const std::u32string what_tab = what + U"(zr)";

    vector<ctstring> strings = LoadZStrings(offs, len, what_tab, Extras_12);

    StartBlock(label, what);

    for(unsigned a=0; a<strings.size(); ++a)
    {
        std::u32string line = Get12string(strings[a], dolf);
        PutBase62Label(offs + a*2);
        PutContent(line, dolf);
    }

    EndBlock();
    MessageDone();
}

void DumpC8String(const unsigned ptroffs,
                  const std::u32string& what)
{
    unsigned offs = (ROM[ptroffs+2 ] << 16)
                  | (ROM[ptroffs+1 ] << 8)
                  | (ROM[ptroffs+0 ]);
    offs = SNES2ROMaddr(offs);

    MessageBeginDumpingStrings(offs);

    vector<unsigned char> Buffer;
    unsigned orig_bytes = (unsigned) Uncompress(ROM+offs, Buffer, ROM+GetROMsize());
    unsigned new_bytes = (unsigned) Buffer.size();

    MarkFree(offs, orig_bytes, what);

    MessageBeginDumpingStrings(offs);

    StartBlock(U"c", what);

    /* The hardcoded numbers between 8A..B8
     * are for the character names that are
     * in the compressed block that comes
     * originally from address DB0000.
     */

    PutBase62Label(ptroffs);
    std::u32string line;
    std::size_t l=0;
    for(unsigned a=0; a<new_bytes; ++a)
    {
        std::size_t l0 = line.size();

        ctchar k = Buffer[a];

        bool special_region = a >= 0x89 && a <= 0xB9;

        char32_t tmp = ilseq;
        if(special_region) tmp = getchar32_t(k, cset_8pix);
        if(tmp == ilseq)
        {
            if(k < 10 && special_region)
                line += wformat(U"[%X]", k);
            else
                line += wformat(U"[%02X]", k);
        }
        else
            line += tmp;

        l += line.size()-l0;
        bool wrap = l>=64;
        if(special_region && (a%6 == 5)) wrap=true;
        if(wrap) { line += U"\n"; l=0; }
    }
    PutContent(line, true);

    EndBlock();

    MessageDone();
}

void Dump8Strings(const unsigned offs,
                  const std::u32string& what,
                  unsigned len)
{
    MessageBeginDumpingStrings(offs);

    const std::u32string what_tab = what + U"(r)";

    vector<ctstring> strings = LoadZStrings(offs, len, what_tab, Extras_8);

    StartBlock(U"r", what);

    for(unsigned a=0; a<strings.size(); ++a)
    {
        std::u32string line = Get8string(strings[a]);
        PutBase62Label(offs + a*2);
        PutContent(line, true);
    }
    EndBlock();

    MessageDone();
}

void DumpFStrings(unsigned offs,
                  const std::u32string& what,
                  unsigned len,
                  unsigned maxcount)
{
    MessageBeginDumpingStrings(offs);

    const std::u32string what_tab = what + U"(f)";

    vector<ctstring> strings = LoadFStrings(offs, len, what_tab, maxcount);

    StartBlock(U"l%u", what, len);

    for(unsigned a=0; a<strings.size(); ++a)
    {
        const ctstring &s = strings[a];

        std::u32string line;
        for(unsigned b=0; b<s.size(); ++b)
            line += Disp8Char(s[b]);

        PutBase62Label(offs + a*len);
        PutContent(line, false);
    }
    EndBlock();
    MessageDone();
}
