// XDrawChem
// Copyright (C) 2004-2005  Bryan Herger <bherger@users.sourceforge.net>
// Copyright (C) 2020  Yaman Qalieh <ybq987@gmail.com>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

// CDXML_Reader.cpp - function definitions for class CDXML_Reader

#include "cdxml_reader.h"
#include "chemdata.h"
#include "defs.h"
#include "dpoint.h"
#include "drawable.h"
#include "text.h"

extern Preferences preferences;

CDXML_Reader::CDXML_Reader(ChemData *c1) {
    nodedepth = 0;
    fragdepth = 0;
    c = c1;
    colors.append({0, QColor(0, 0, 0)});
    colors.append({1, QColor(255, 255, 255)});
}

bool CDXML_Reader::ReadFile(QString fn) {
    qDebug() << Qt::endl << "New and improved CDXML parser" << Qt::endl;
    int i1 = fn.indexOf("<CDXML");
    int i2 = fn.indexOf("</CDXML>") + 8;

    ParseDocument(fn.mid(i1, i2 - i1));
    Build();
    qDebug() << Qt::endl << "Done parsing!" << Qt::endl;
    return false;
}

void CDXML_Reader::ParseDocument(QString dtag) {
    int i1, i2;

    // find and parse color table
    i1 = dtag.indexOf("<colortable>");
    i2 = dtag.indexOf("</colortable>") + 13;
    if (i1 >= 0) {
        ParseColorTable(dtag.mid(i1, i2 - i1));
        dtag.remove(i1, i2 - i1);
    }
    // if no color table, set default foreground and background
    if (colors.count() < 3) {
        colors.append({2, QColor(255, 255, 255)}); // background
        colors.append({3, QColor(0, 0, 0)});       // foreground
    }
    // find and parse font table
    i1 = dtag.indexOf("<fonttable>");
    i2 = dtag.indexOf("</fonttable>") + 12;
    if (i1 >= 0) {
        ParseFontTable(dtag.mid(i1, i2 - i1));
        dtag.remove(i1, i2 - i1);
    }
    // find and parse page(s)
    do {
        i1 = dtag.indexOf("<page");
        i2 = dtag.indexOf("</page>") + 7;
        if (i1 < 0)
            break;
        ParsePage(dtag.mid(i1, i2 - i1));
        dtag.remove(i1, i2 - i1);
    } while (1);
}

void CDXML_Reader::ParseColorTable(QString ctable) {
    QString nexttag;
    int ptr = 0, idx = 2;

    readTag(ctable, ptr); // Discard <colortable
    while ((nexttag = readTag(ctable, ptr)) != "</colortable>") {
        if (!selfContainedTag(nexttag))
            continue;

        colors.append({idx++, ParseColor(nexttag)});
    }
}

QColor CDXML_Reader::ParseColor(QString ctag) {
    QStringList attr;
    QColor retval;
    int re, gr, bl;

    attr = readAttr(ctag);
    for (int cc = 0; cc < 5; cc = cc + 2) {
        if (attr[cc] == "r")
            re = (int)(attr[cc + 1].toDouble() * 255.0);
        else if (attr[cc] == "g")
            gr = (int)(attr[cc + 1].toDouble() * 255.0);
        else if (attr[cc] == "b")
            bl = (int)(attr[cc + 1].toDouble() * 255.0);
    }
    retval.setRgb(re, gr, bl);
    return retval;
}

void CDXML_Reader::ParseFontTable(QString ftable) {
    QString nexttag;
    int ptr = 0;

    readTag(ftable, ptr); // Discard <fonttable
    while ((nexttag = readTag(ftable, ptr)) != "</fonttable") {
        if (!selfContainedTag(nexttag))
            continue;

        fonts.append(ParseFont(nexttag));
    }
}

FontTableEntry CDXML_Reader::ParseFont(QString ftag) {
    QStringList attr;

    attr = readAttr(ftag);
    FontTableEntry ret;

    for (int cc = 0; cc < attr.count(); cc = cc + 2) {
        if (attr[cc] == "name") {
            ret.fam = attr[cc + 1];
        } else if (attr[cc] == "id") {
            ret.id = attr[cc + 1];
        }
    }

    return ret;
}

void CDXML_Reader::ParsePage(QString ptag) {
    // parse out known subobjects
    int i1, i2;
    bool flag;

    do {
        flag = false;
        // find fragments
        i1 = ptag.indexOf("<fragment");
        if (i1 >= 0) {
            i2 = i1 + positionOfEndTag(ptag.mid(i1), "fragment");
            ParseFragment(ptag.mid(i1, i2 - i1));
            ptag.remove(i1, i2 - i1);
            flag = true;
        }
        // find text
        // i1 = ptag.indexOf("<t");
        // if (i1 >= 0) {
        //  i2 = ptag.indexOf("</t>") + 4;
        //  ParseText(ptag.mid(i1, i2 - i1));
        //  ptag.remove(i1, i2 - i1);
        //  flag = true;
        //}
    } while (flag); // repeat as necessary
    // find text and graphics not contained in fragments
    do {
        flag = false;
        i1 = ptag.indexOf("<t");
        if (i1 >= 0) {
            i2 = i1 + positionOfEndTag(ptag.mid(i1), "t");
            qDebug() << "toplevel";
            QString rs = ParseText(ptag.mid(i1, i2 - i1));

            DPoint *curr_node = new DPoint;
            curr_node->x = globalx;
            curr_node->y = globaly;
            curr_node->element = rs;
            nodelist.append(curr_node);
            ptag.remove(i1, i2 - i1);
            flag = true;
        }
        i1 = ptag.indexOf("<graphic");
        if (i1 >= 0) {
            i2 = ptag.indexOf(">", i1) + 1;
            if (!selfContainedTag(ptag.mid(i1, i2 - i1)))
                qDebug() << "Malformed <graphic>";
            qDebug() << "toplevel";
            ParseGraphic(ptag.mid(i1, i2 - i1));
            ptag.remove(i1, i2 - i1);
            flag = true;
        }
    } while (flag);
}

// warning, fragments can contain nodes, which in turn can contain fragments.
// make sure you get the level right
void CDXML_Reader::ParseFragment(QString ftag) {
    fragdepth++;
    qDebug() << Qt::endl << "<--frag-->";
    qDebug() << ftag << Qt::endl << Qt::endl;

    int i1, i2;
    bool flag;

    do {
        flag = false;
        // find nodes
        i1 = ftag.indexOf("<n");
        i2 = ftag.indexOf(">", i1) + 1;
        if (i1 >= 0) {
            if (!selfContainedTag(ftag.mid(i1, i2 - i1)))
                i2 = i1 + positionOfEndTag(ftag.mid(i1), "n");
            ParseNode(ftag.mid(i1, i2 - i1));
            ftag.remove(i1, i2 - i1);
            flag = true;
        }
    } while (flag);
    do {
        flag = false;
        // find bonds (typically self-contained)
        i1 = ftag.indexOf("<b");
        i2 = ftag.indexOf(">", i1) + 1;
        if (i1 >= 0) {
            if (!selfContainedTag(ftag.mid(i1, i2 - i1)))
                qDebug() << "Malformed <b>";
            ParseBond(ftag.mid(i1, i2 - i1));
            ftag.remove(i1, i2 - i1);
            flag = true;
        }
        // find graphics
        i1 = ftag.indexOf("<graphic");
        if (i1 >= 0) {
            i2 = ftag.indexOf(">", i1) + 1;
            if (!selfContainedTag(ftag.mid(i1, i2 - i1)))
                qDebug() << "Malformed <graphic>";
            ParseGraphic(ftag.mid(i1, i2 - i1));
            ftag.remove(i1, i2 - i1);
            flag = true;
        }
    } while (flag);
    fragdepth--;
}

// note this reads <t> and the contained <s>
QString CDXML_Reader::ParseText(QString ttag) {
    qDebug() << Qt::endl << "<--text-->";
    qDebug() << ttag << Qt::endl;

    int i1, i2, i3;
    QString a1, v1;

    // tokenize the <t> tag
    QStringList t_tokens;

    i1 = ttag.indexOf(">");
    t_tokens = readAttr(ttag.left(i1 + 1));
    qDebug() << "<--t_tokens-->";
    for (int c1 = 0; c1 < t_tokens.count(); c1 += 2) {
        a1 = t_tokens.at(c1);
        v1 = t_tokens.at(c1 + 1);
        qDebug() << "-" << a1.toLatin1() << "|" << v1.toLatin1() << "-";
        // compare attribute a1 with list of relevant attr's
        if (a1.toUpper() == QString("P")) {
            i1 = v1.indexOf(QString(" "));
            globalx = v1.mid(0, i1).toDouble();
            globaly = v1.mid(i1 + 1).toDouble();
        }
    }
    // tokenize the <s> tag
    QString actualtext;

    // QStringList s_tokens;
    // i1 = ttag.indexOf("<s ");
    // i2 = ttag.indexOf(">", i1) + 1;
    // s_tokens = readAttr(ttag.mid(i1, i2 - i1));
    // qDebug() << "<--s_tokens-->" ;
    // for (int c1 = 0; c1 < s_tokens.count(); c1 += 2) {
    //  a1 = *(s_tokens.at(c1));
    //  v1 = *(s_tokens.at(c1 + 1));
    //  qDebug() << "-" << a1.toLatin1() << "|" << v1.toLatin1() << "-" ;
    // compare attribute a1 with list of relevant attr's
    //}
    // read actual text
    // i1 = i2;
    // i2 = ttag.indexOf("<", i1);
    // QString actualtext = ttag.mid(i1, i2 - i1);
    while ((i1 = ttag.indexOf("<s ")) > 0) {
        i2 = ttag.indexOf(">", i1);
        i3 = ttag.indexOf("</s>", i2);
        actualtext.append(ttag.mid(i2 + 1, i3 - i2 - 1));
        ttag.remove(i1, i3 - i1 + 4);
        qDebug() << ttag;
    };
    qDebug() << "string = " << actualtext;
    return actualtext;
}

void CDXML_Reader::ParseGraphic(QString gtag) {
    CDXML_Object *curr_obj = new CDXML_Object;
    curr_obj->type = TYPE_DRAWABLE;

    // tokenize the <graphic> tag
    QStringList tokens;
    QString a1, v1;
    int i1;
    DPoint *s1, *e1;

    i1 = gtag.indexOf(">");
    tokens = readAttr(gtag.left(i1 + 1));
    qDebug() << "<--graphictokens-->";
    for (int c1 = 0; c1 < tokens.count(); c1 += 2) {
        a1 = tokens.at(c1);
        v1 = tokens.at(c1 + 1);
        qDebug() << "-" << a1.toLatin1() << "|" << v1.toLatin1() << "-";
        // compare attribute a1 with list of relevant attr's
        if (a1.toUpper() == "BOUNDINGBOX") {
            s1 = new DPoint;
            e1 = new DPoint;
            QTextStream coords(&v1, QIODevice::ReadOnly);

            coords >> s1->x >> s1->y >> e1->x >> e1->y;
            // qDebug() << s1->x << " ";
            // qDebug() << s1->y << " ";
            // qDebug() << e1->x << " ";
            // qDebug() << e1->y ;
            curr_obj->start = s1;
            curr_obj->end = e1;
        }
        // don't let GraphicType override special types...
        if ((a1.toUpper() == "GRAPHICTYPE") && (curr_obj->type == TYPE_DRAWABLE)) {
            if (v1.toUpper() == "BRACKET")
                curr_obj->type = TYPE_BRACKET;
        }
        if (a1.toUpper() == "ARROWTYPE") {
            curr_obj->type = TYPE_ARROW;
            curr_obj->idata2 = ARROW_REGULAR;
            if (v1.toUpper() == "RESONANCE")
                curr_obj->idata2 = ARROW_BI1;
            if (v1.toUpper() == "EQUILIBRIUM")
                curr_obj->idata2 = ARROW_BI2;
            if (v1.toUpper() == "RETROSYNTHETIC")
                curr_obj->idata2 = ARROW_RETRO;
        }
        if (a1.toUpper() == "BRACKETTYPE") {
            curr_obj->idata2 = BRACKET_SQUARE;
            if (v1.toUpper() == "SQUAREPAIR")
                curr_obj->type = TYPE_BRACKET;
            if (v1.toUpper() == "ROUNDPAIR") {
                curr_obj->type = TYPE_BRACKET;
                curr_obj->idata2 = BRACKET_CURVE;
            }
            if (v1.toUpper() == "CURLYPAIR") {
                curr_obj->type = TYPE_BRACKET;
                curr_obj->idata2 = BRACKET_BRACE;
            }
        }
    }

    objectlist.append(curr_obj);
}

// warning, nodes can contain fragments, which in turn can contain nodes.
// make sure you get the level right
void CDXML_Reader::ParseNode(QString ntag) {
    DPoint *curr_node = new DPoint;

    nodedepth++;
    qDebug() << Qt::endl << "<--node-->";
    qDebug() << ntag << Qt::endl << Qt::endl;

    int i1, i2;
    bool flag;

    // tokenize the <n> tag
    QStringList tokens;
    QString a1, v1, nodetype;

    i1 = ntag.indexOf(">");
    tokens = readAttr(ntag.left(i1 + 1));
    qDebug() << "<--nodetokens-->";
    for (int c1 = 0; c1 < tokens.count(); c1 += 2) {
        a1 = tokens.at(c1);
        v1 = tokens.at(c1 + 1);
        qDebug() << "-" << a1.toLatin1() << "|" << v1.toLatin1() << "-";
        // compare attribute a1 with list of relevant attr's
        if (a1.toUpper() == QString("ID"))
            curr_node->id = v1;
        if (a1.toUpper() == QString("NODETYPE"))
            nodetype = v1;
        if (a1.toUpper() == QString("P")) {
            i1 = v1.indexOf(QString(" "));
            curr_node->x = v1.mid(0, i1).toDouble();
            curr_node->y = v1.mid(i1 + 1).toDouble();
        }
    }

    do {
        flag = false;
        // find nodes
        i1 = ntag.indexOf("<fragment");
        if (i1 >= 0) {
            i2 = i1 + positionOfEndTag(ntag.mid(i1), "fragment");
            // right now, we only care about top-level fragment/nodes
            // ParseFragment(ntag.mid(i1, i2 - i1));
            ntag.remove(i1, i2 - i1);
            flag = true;
        }
        // find text (strings)
        i1 = ntag.indexOf("<t");
        if (i1 >= 0) {
            i2 = i1 + positionOfEndTag(ntag.mid(i1), "t");
            QString rs = ParseText(ntag.mid(i1, i2 - i1));

            curr_node->element = rs;
            ntag.remove(i1, i2 - i1);
            flag = true;
        }
    } while (flag);
    nodedepth--;

    nodelist.append(curr_node);
}

void CDXML_Reader::ParseBond(QString btag) {
    CDXML_Object *curr_obj = new CDXML_Object;
    curr_obj->type = TYPE_BOND;
    curr_obj->idata1 = 1;

    // tokenize the <b> tag
    QStringList tokens;
    QString a1, v1;
    int i1;

    i1 = btag.indexOf(">");
    tokens = readAttr(btag.left(i1 + 1));
    qDebug() << "<--bondtokens-->";
    for (int c1 = 0; c1 < tokens.count(); c1 += 2) {
        a1 = tokens.at(c1);
        v1 = tokens.at(c1 + 1);
        qDebug() << "-" << a1.toLatin1() << "|" << v1.toLatin1() << "-";
        // compare attribute a1 with list of relevant attr's
        if (a1.toUpper() == QString("ID"))
            curr_obj->id = v1;
        if (a1.toUpper() == QString("B"))
            curr_obj->start_id = v1;
        if (a1.toUpper() == QString("E"))
            curr_obj->end_id = v1;
        if (a1.toUpper() == QString("ORDER"))
            curr_obj->idata1 = v1.toUInt();
        if (a1.toUpper() == QString("DISPLAY")) {
            if (v1 == QString("WedgeBegin"))
                curr_obj->idata1 = 5;
            if (v1 == QString("WedgedHashBegin"))
                curr_obj->idata1 = 7;
        }
    }

    objectlist.append(curr_obj);
}

DPoint *CDXML_Reader::FindNode(QString key) {
    for (DPoint *curr_node : nodelist) {
        if (curr_node->id == key)
            return curr_node;
    }
    return 0;
}

// build structure from data we read
void CDXML_Reader::Build() {
    QList<DPoint *> up;
    DPoint *s1, *e1;
    Text *nt;
    double bondlength = 0.0;
    int nbonds = 0;

    qDebug() << "nodes: " << nodelist.count();
    qDebug() << "objects: " << objectlist.count();

    // add all non-text objects
    for (CDXML_Object *curr_obj : objectlist) {
        if (curr_obj->type == TYPE_BOND) {
            s1 = FindNode(curr_obj->start_id);
            e1 = FindNode(curr_obj->end_id);
            bondlength += s1->distanceTo(e1);
            nbonds++;
            c->addBond(s1, e1, 1, curr_obj->idata1, QColor(0, 0, 0), true);
            s1->hit = true;
            e1->hit = true;
            if (up.contains(s1))
                up.append(s1);
            if (up.contains(e1))
                up.append(e1);
        }
        if (curr_obj->type == TYPE_ARROW) {
            c->addArrow(curr_obj->end, curr_obj->start, QColor(0, 0, 0), curr_obj->idata2, true);
            if (up.contains(curr_obj->start))
                up.append(curr_obj->start);
            if (up.contains(curr_obj->end))
                up.append(curr_obj->end);
        }
        if (curr_obj->type == TYPE_BRACKET) {
            c->addBracket(curr_obj->start, curr_obj->end, QColor(0, 0, 0), curr_obj->idata2, true);
            if (up.contains(curr_obj->start))
                up.append(curr_obj->start);
            if (up.contains(curr_obj->end))
                up.append(curr_obj->end);
        }
    }
    // add text
    for (DPoint *curr_node : nodelist) {
        if (curr_node->element != "C") {
            nt = new Text(c->getRender2D());
            nt->setPoint(curr_node);
            if (up.contains(curr_node))
                up.append(curr_node);
            if (curr_node->hit) { // true = part of molecule
                nt->setJustify(JUSTIFY_CENTER);
            } else { // false = free-standing text
                nt->setJustify(JUSTIFY_TOPLEFT);
            }
            nt->setText(curr_node->element);
            curr_node->element.fill(' ');
            //            nt->setTextMask( curr_node->element );
            nt->Highlight(true);
            c->addText(nt);
        }
    }
    // clear "hit" flag
    for (DPoint *curr_node : nodelist) {
        curr_node->hit = false;
    }
    // scale drawing
    double avglen = bondlength / (double)nbonds;
    double curfixed = preferences.getBond_fixedlength();
    double sf = curfixed / avglen;

    qDebug() << "Scale:" << sf;
    // sf = 1.0;
    double sl = 9999.0, sr = -9999.0, st = 9999.0, sb = -9999.0;

    for (DPoint *curr_node : up) {
        curr_node->x *= sf;
        curr_node->y *= sf;
        if (curr_node->x < sl)
            sl = curr_node->x;
        if (curr_node->x > sr)
            sr = curr_node->x;
        if (curr_node->y < st)
            st = curr_node->y;
        if (curr_node->y > sb)
            sb = curr_node->y;
    }
    double tx = 50 - sl;
    double ty = 50 - st;

    for (DPoint *curr_node : up) {
        curr_node->x += tx;
        curr_node->y += ty;
    }
}
