#include <cstdio>
#include <iostream>
#include <sstream>
#include <map>
#include <list>
#include <vector>
#include <bitset>

#include "xml.hh"
#include "wstring.hh"

/* Maximum number of opcodes allowed.
 * For std::bitset.
 */
#define MAX_OPS 16384

static unsigned CurSeqNo;
static std::vector<std::u32string> CurSeq;

struct Cmd
{
    std::u32string cmd;
    std::u32string size;
};

static std::map<std::u32string, std::u32string> parammap;
static std::vector<Cmd> cmds;
static std::map<std::u32string, std::list<unsigned> > ops;

using u32stringstream = std::basic_stringstream<char32_t>;

static void BeginParam(u32stringstream& cmdbuf,
                       const std::u32string& /*pos*/, const std::u32string& /*name*/)
{
    /*
        add(data, pos, name)
        add(data, pos)
        add(data, name)
     */
    cmdbuf << "\t\tcmd.Add(";
}
static void EndParam(u32stringstream& cmdbuf,
                     const std::u32string& pos, const std::u32string& name)
{
    if(!pos.empty()) cmdbuf << ", " << pos << "U";
    if(!name.empty()) cmdbuf << ", '" << name << "'";

    if(name.empty()) cmdbuf << " /* nameless */";
    if(pos.empty())  cmdbuf << " /* virtual */";

    cmdbuf << ");\n";
}

static const std::u32string HexNum(const std::u32string& input)
{
    u32stringstream s;
    unsigned val;

    s.flags(std::ios_base::hex);
    s << input;
    s >> val;

    if(val > 0 && val < 0xFF)
    {
        int diff = val-CurSeqNo;
        if(!diff) return U"a";
        if(diff > -30 && diff < 80)
            return wformat(U"a%+d", diff);
    }
    return wformat(U"0x%X", val);
}

static const std::u32string TranslateCmd(const std::u32string& cmd)
{
    parammap.clear();
    unsigned n=0;
    std::u32string result;
    const char32_t* fmtptr = cmd.c_str();
    while(*fmtptr)
    {
        if(*fmtptr == U'%')
        {
            std::u32string paramname;
            while(std::isalnum(*++fmtptr)) paramname += *fmtptr;
            std::u32string newname;
            newname += U'a'+(n++);
            parammap[paramname] = newname;
            result += U'%';
            result += newname;
        }
        else
            result += *fmtptr++;
    }
    return result;
}

static void DumpParam(const XMLTreeP treep)
{
    u32stringstream cmdbuf;

    const XMLTree& tree = *treep;

    const std::u32string pos = tree[U"pos"];
    const std::u32string type = tree[U"type"];
    const std::u32string name = parammap[tree[U"param"]];

    BeginParam(cmdbuf, pos, name);

    if(type == U"Immed")
    {
        std::u32string min = tree[U"min"];
        std::u32string max = tree[U"max"];
        if(min.empty()) min=U"0";
        if(max.empty())
        {
            max = tree[U"size"];
            max = max == U"2" ? U"FFFF" : U"FF";
        }

        cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                                  << "," << HexNum(min)
                                  << "," << HexNum(max)
                                  << ",0,0)";
    }
    else if(type == U"ObjectNumber")
    {
        cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                                  << ",0x00,0x7E,0,-1)";
    }
    else if(type == U"NibbleHi")
    {
        cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                                  << ",0x00,0xFF, 0,0, ElemData::t_nibble_hi)";
    }
    else if(type == U"NibbleLo")
    {
        cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                                  << ",0x00,0xFF, 0,0, ElemData::t_nibble_lo)";
    }
    else if(type == U"OrBitNumber")
    {
        cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                                  << ",0x00,0x07, 0,0, ElemData::t_orbit)";
    }
    else if(type == U"AndBitNumber")
    {
        cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                                  << ",0x00,0x07, 0,0, ElemData::t_andbit)";
    }
    else if(type == U"TableReference")
    {
        std::u32string max = tree[U"size"];
        max = max == U"2" ? U"FFFF" : U"FF";

        cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                                  << ", 0," << HexNum(max)
                                  << ", " << HexNum(tree[U"address"])
                                  << ",0)";
    }
    else if(type == U"Table2Reference")
    {
        std::u32string max = tree[U"size"];
        max = max == U"2" ? U"FFFF" : U"FF";

        cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                                  << ", 0," << HexNum(max)
                                  << ", " << HexNum(tree[U"address"])
                                  << ",1)";
    }
    else if(type == U"Prop")
    {
        const std::u32string value = tree[U"value"];
        cmdbuf << "ElemData(" << value.size()/2
                                  << "," << HexNum(value)
                                  << "," << HexNum(value)
                                  << ",0,0)";
    }
    else if(type == U"Const")
    {
        const std::u32string value = tree[U"value"];
        cmdbuf << "ElemData(" << value.size()/2
                                  << "," << HexNum(value)
                                  << "," << HexNum(value)
                                  << ",0,0)";
    }
    else if(type == U"GotoForward")
    {
        cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                   << ",0x00,0xFF,0,0, ElemData::t_else)";
    }
    else if(type == U"GotoBackward")
    {
        cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                   << ",0x00,0xFF,0,0, ElemData::t_loop)";
    }
    else if(type == U"ConditionalGoto")
    {
        cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                   << ",0x00,0xFF,0,0, ElemData::t_if)";
    }
    else if(type == U"Operator")
    {
        cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                   << ",0x00,0x7F,0,0, ElemData::t_operator)";
    }
    else if(type == U"Blob")
    {
        const std::u32string textflag = tree[U"text"];
        if(textflag.empty())
            cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                       << ",0x0000,0xFFFF,0,0, ElemData::t_blob)";
        else
            cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                       << ",0x0000,0xFFFF,0,0, ElemData::t_textblob)";
    }
    else if(type == U"DialogBegin")
    {
        cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                                  << ",0x00,0xFFFFFF, 0,0, ElemData::t_dialogbegin)";
    }
    else if(type == U"DialogIndex")
    {
        cmdbuf << "ElemData(" << (std::u32string)tree[U"size"]
                                  << ",0x00,0xFF, 0,0, ElemData::t_dialogaddr)";
    }
    else
    {
        std::cerr << "Unknown type '" << WstrToAsc(type) << "'\n";
    }

    const std::u32string highbitflag = tree[U"highbit"];
    if(!highbitflag.empty())
    {
        cmdbuf << ".DeclareHighbit()";
    }

    EndParam(cmdbuf, pos, name);

    CurSeq.push_back(cmdbuf.str());
}
static void DumpSequence(const XMLTreeP treep)
{
    const XMLTree& tree = *treep;

    Cmd cmd;
    cmd.cmd  = TranslateCmd(tree[U"cmd"]);
    cmd.size = tree[U"length"];

    CurSeq.clear();
    CurSeqNo = (unsigned) cmds.size();

    tree.ForEach(U"param", DumpParam);

    for(unsigned a=0; a<CurSeq.size(); ++a)
        ops[CurSeq[a]].push_back(CurSeqNo);

    cmds.push_back(cmd);
}

struct bitsetcompare
{
    bool operator() (const std::bitset<MAX_OPS>& a, const std::bitset<MAX_OPS>& b) const
    {
        return a.to_string<char, std::char_traits<char>, std::allocator<char> >()
             < b.to_string<char, std::char_traits<char>, std::allocator<char> >();
    }
};

static void DumpSeqs()
{
    std::cout << "static const struct { unsigned length; const char *format; } "
                  "ops[" << cmds.size() << "] =\n"
                  "{\n";
    for(unsigned a=0; a<cmds.size(); ++a)
    {
        std::cout << "\t{ /*" << a << "*/"
                      "  " << WstrToAsc(cmds[a].size) <<
                      ", \"" << WstrToAsc(cmds[a].cmd) << "\""
                      " },\n";
    }
    std::cout << "};\n";
    std::cout << "for(unsigned a=0; a<" << cmds.size() << "; ++a) {\n";

    std::cout << "\tCommand cmd(ops[a].format);\n";
    std::cout << "\tcmd.SetSize(ops[a].length);\n";

    typedef std::map<std::bitset<MAX_OPS>, std::u32string, bitsetcompare> ops2_t;
    ops2_t ops2;

    for(std::map<std::u32string, std::list<unsigned> >::const_iterator
        i = ops.begin(); i != ops.end(); ++i)
    {
        std::bitset<MAX_OPS> bset;
        for(std::list<unsigned>::const_iterator
            j = i->second.begin();
            j != i->second.end();
            ++j)
        {
            bset.set(*j);
        }
        ops2[bset] += i->first;
    }

    /* Process each set of noncolliding bitsets into switch-cases,
     * until there are none. */
    while(!ops2.empty())
    {
        std::bitset<MAX_OPS> test;

        std::cout << "\tswitch(a) {\n";
        for(ops2_t::iterator j, i = ops2.begin(); i != ops2.end(); i=j)
        {
            j = i; ++j;

            if((test & i->first).any()) continue;

            for(unsigned a=0; a<cmds.size(); ++a)
                if(i->first.test(a))
                    std::cout << "\tcase " << a << ":\n";

            std::cout << WstrToAsc(i->second);
            std::cout << "\t\tbreak;\n";

            test |= i->first;

            ops2.erase(i);
        }
        std::cout << "\t}\n";
    }

    std::cout << "\tcmd.PutInto(OPTree);\n";
    std::cout << "\tcmd.PutInto(STRTree);\n";
    std::cout << "}\n";
}

static void DumpAddress(const XMLTreeP treep)
{
    const XMLTree& tree = *treep;

    std::u32string addr = tree[U"addr"];
    std::u32string name = tree[U"name"];

    if(!addr.empty() && !name.empty())
    {
        std::cout <<
            "MemoryAddressConstants.Define"
            "(0x" << WstrToAsc(addr) << ", U\"" << WstrToAsc(name) << "\");\n";
    }
}

int main(void)
{
    std::string xml;
    while(!std::feof(stdin))
    {
        char Buf[8192];
        std::size_t n = std::fread(Buf, 1, sizeof Buf, stdin);
        xml += std::string(Buf, n);
    }

    std::cout << "/* Automatically generated by chronotools/utils/eventsynmake */\n\n";

    XMLTree tree = ParseXML(xml);

    tree[U"location_event_commands"].ForEach(U"sequence", DumpSequence);
    tree[U"location_event_commands"][U"memory_addresses"].ForEach(U"address", DumpAddress);

    DumpSeqs();

    return 0;
}
