// 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/>.

#include "chemdata.h"
#include "arrow.h"
#include "biotools.h"
#include "bond.h"
#include "bracket.h"
#include "curvearrow.h"
#include "defs.h"
#include "drawable.h"
#include "molecule.h"
#include "symbol.h"
#include "text.h"

ChemData::ChemData(QObject *parent) : QObject(parent) {
    thick_kludge = -1;
    notSaved = false;

    // Initialize Undo
    undo_index = -1;
}

ChemData::~ChemData() {
    if (clip && !clip->owners)
        delete clip;
}

void ChemData::drawAll() {
    // draw all objects in ChemData
    for (Drawable *curr_draw : drawlist) {
        curr_draw->Render();
    }
}

// update Molecules after move
void ChemData::FinishMove() {
    for (Drawable *curr_draw : drawlist) {
        if (curr_draw->Type() == TYPE_MOLECULE) {
            static_cast<Molecule *>(curr_draw)->Changed();
        }
    }

    notSaved = true;
}

Molecule *ChemData::firstMolecule() {
    for (Drawable *curr_draw : drawlist) {
        if (curr_draw->Type() == TYPE_MOLECULE) {
            return static_cast<Molecule *>(curr_draw);
        }
    }
    return nullptr;
}

void ChemData::addMolecule(Molecule *m1) {
    drawlist.append(m1);
    notSaved = true;
}

void ChemData::addArrow(DPoint *s, DPoint *e, QColor c, int t, int p2, bool hl) {
    Arrow *a1 = new Arrow(r);

    a1->setPoints(s, e);
    a1->SetColor(c);
    a1->SetStyle(t);
    a1->setThick(p2);
    if (hl)
        a1->Highlight(true);
    drawlist.append(a1);
    notSaved = true;
}

void ChemData::addCurveArrow(DPoint *s, DPoint *e, QColor c, QString s1, bool hl) {
    CurveArrow *a1 = new CurveArrow(r);

    a1->setPoints(s, e);
    a1->SetColor(c);
    a1->SetCurve(s1);
    if (hl)
        a1->Highlight(true);
    drawlist.append(a1);
    notSaved = true;
}

void ChemData::addBracket(DPoint *s, DPoint *e, QColor c, int type, bool hl) {
    Bracket *a1 = new Bracket(r);

    a1->setPoints(s, e);
    a1->SetColor(c);
    a1->SetStyle(type);
    if (hl)
        a1->Highlight(true);
    drawlist.append(a1);
    notSaved = true;
}

void ChemData::addText(Text *t) {
    qDebug() << "addText";
    if (t->Justify() == JUSTIFY_TOPLEFT) { // add to drawing
        drawlist.append(t);
    } else { // add label to specific Molecule
        for (Drawable *curr_draw : drawlist) {
            if (curr_draw->Find(t->Start()) == true) {
                Molecule *tm = (Molecule *)curr_draw; // this is cheating, I know!

                tm->addText(t);
                return;
            }
        }
        qDebug() << "FYI, add text failed";
    }
    notSaved = true;
}

void ChemData::addGraphicObject(GraphicObject *t) {
    drawlist.append(t);
    notSaved = true;
}

void ChemData::addBond(DPoint *s, DPoint *e, int thick, int order, QColor c, bool hl) {
    // qInfo() << "Request to add bond:" << s->element << "(" << s->x << "," <<
    // s->y << ")-" << e->element << "(" << e->x << "," << e->y << "), order "
    // << order;
    Drawable *m1 = 0, *m2 = 0;

    for (Drawable *curr_draw : drawlist) {
        if (curr_draw->Find(s))
            m1 = curr_draw;

        if (curr_draw->Find(e))
            m2 = curr_draw;
    }
    // qInfo() << "m1 = " << (m1 == 0 ? -1 : m1->Type()) << ", m2 = " << (m2 ==
    // 0 ? -1 : m2->Type());
    // neither point exists -- create new Molecule
    if ((m1 == 0) && (m2 == 0)) {
        // qInfo() << "neither point exists, create new Molecule";
        Molecule *m = new Molecule(r);
        m->SetChemdata(this);
        m->addBond(s, e, thick, order, c, hl);
        drawlist.append(m);
        notSaved = true;
        return;
    }
    // one point exists, or both in same molecule
    if ((m1 == 0) && (m2 != 0)) {
        m1 = m2;
        m2 = 0;
    }
    if (((m1 != 0) && (m2 == 0)) || (m1 == m2)) {
        // qInfo() << "one point exists, or both in same molecule";
        m1->addBond(s, e, thick, order, c, hl);
        notSaved = true;
        return;
    }
    // both points exist in different molecules
    if (m1 != m2) {
        // qInfo() << "both points exist in different molecules";
        m1->addBond(s, e, thick, order, c, hl);
        m1->addMolecule(m2); // before or after addBond?
        drawlist.removeAll(m2);
        delete m2;
    }
    notSaved = true;
}

void ChemData::addSymbol(DPoint *a, QString symbolfile, bool hl) {
    Symbol *s1 = new Symbol(r);
    s1->setPoint(a);
    s1->SetSymbol(symbolfile);
    if (hl)
        s1->Highlight(true);
    // determine whether point exists or not; if exists, add to Molecule
    for (Drawable *curr_draw : drawlist) {
        if ((curr_draw->Find(a)) && (curr_draw->Type() == TYPE_MOLECULE)) {
            static_cast<Molecule *>(curr_draw)->addSymbol(s1);
            return;
        }
    }
    drawlist.append(s1);
    notSaved = true;
}

Molecule *ChemData::insideMolecule(DPoint *t1) {
    // qDebug() << t1->x << "," << t1->y;
    for (Drawable *curr_draw : drawlist) {
        if (curr_draw->Type() == TYPE_MOLECULE) {
            Molecule *m1 = (Molecule *)curr_draw;
            // QRect tr1 = m1->BoundingBoxAll();
            // qDebug() << tr1.left() << "," << tr1.top() << ";";
            // qDebug() << tr1.right() << "," << tr1.bottom();
            if (m1->BoundingBoxAll().contains(t1->toQPoint(), false))
                return m1;
        }
    }
    return 0;
}

DPoint *ChemData::FindNearestPoint(DPoint *target, double &dist) {
    DPoint *nearest = 0, *d1;
    double mindist = 9999.0, d1dist = 999999.0;
    for (Drawable *curr_draw : drawlist) {
        d1 = curr_draw->FindNearestPoint(target, d1dist);
        if (d1dist < mindist) {
            mindist = d1dist;
            nearest = d1;
        }
    }
    dist = mindist;
    return nearest;
}

Drawable *ChemData::FindNearestObject(DPoint *target, double &dist) {
    Drawable *nearest = 0, *d1;
    double mindist = 2000.0, d1dist = 999999.0;
    for (Drawable *curr_draw : drawlist) {
        d1 = curr_draw->FindNearestObject(target, d1dist);
        if (d1dist < mindist) {
            mindist = d1dist;
            nearest = d1;
        }
    }
    dist = mindist;
    return nearest;
}

void ChemData::Erase(Drawable *d) {
    QList<Drawable *> removelist;
    bool erased = false;
    if (drawlist.removeAll(d) == false) {
        for (Drawable *curr_draw : drawlist) {
            erased = curr_draw->Erase(d);
            // collect empty Molecules for removal
            if (curr_draw->Members() == 0)
                removelist.append(curr_draw);
            qDebug() << "erased:" << erased;
            if (erased == true)
                break; // should only be one instance of d to remove!
        }
    } else { // drawlist.remove(d) == true
        delete d;
    }
    // remove empty Molecules
    for (Drawable *curr_draw : removelist) {
        drawlist.removeAll(curr_draw);
        delete curr_draw;
    }
    // Split Molecules as needed
    DetectSplit();
    notSaved = true;
}

void ChemData::EraseSelected() {
    QList<Drawable *> removelist;

    for (Drawable *curr_draw : drawlist) {
        if (curr_draw->Type() == TYPE_MOLECULE) {
            static_cast<Molecule *>(curr_draw)->EraseSelected();
            // collect empty Molecules for removal
            if (curr_draw->Members() == 0)
                removelist.append(curr_draw);
        } else {
            if (curr_draw->Highlighted() == true) {
                removelist.append(curr_draw);
            }
        }
    }
    for (Drawable *curr_draw : removelist) {
        /*
           if (curr_draw->Type() == TYPE_TEXT) {
           Text *tmp_text = (Text *)curr_draw;
           if (tmp_text->getDataType() == TEXT_DATA_MW) {
           tmp_text->getMolecule()->MWLabelDeleted();
           }
           if (tmp_text->getDataType() == TEXT_DATA_FORMULA) {
           tmp_text->getMolecule()->FormulaLabelDeleted();
           }
           }
         */
        drawlist.removeAll(curr_draw);
        delete curr_draw;
    }
    // Split Molecules as needed
    DetectSplit();
    notSaved = true;
}

// Split Molecule's which hold multiple structures (e.g. after delete)
void ChemData::DetectSplit() {
    QList<Drawable *> removelist;
    QList<Molecule *> split_list;

    for (Drawable *curr_draw : drawlist) {
        if (curr_draw->Type() == TYPE_MOLECULE) {
            Molecule *tmp_mol = (Molecule *)curr_draw;
            split_list = tmp_mol->MakeSplit();
            if (split_list.count() > 1) {
                qDebug() << "Split needed";
                removelist.append(curr_draw);
                for (Drawable *td2 : split_list) {
                    drawlist.append(td2);
                }
                split_list.clear();
            }
        }
    }
    // remove old Molecules
    for (Drawable *curr_draw : removelist) {
        drawlist.removeAll(curr_draw);
        delete curr_draw;
    }
}

void ChemData::SelectAll() {
    QList<DPoint *> allpts = UniquePoints();

    for (Drawable *curr_draw : drawlist) {
        curr_draw->SelectAll();
    }
    for (DPoint *curr_pt : allpts) {
        curr_pt->setHighlighted(true);
    }
}

void ChemData::DeselectAll() {
    QList<DPoint *> allpts = UniquePoints();

    for (Drawable *curr_draw : drawlist) {
        curr_draw->DeselectAll();
    }
    for (DPoint *curr_pt : allpts) {
        curr_pt->setHighlighted(false);
    }
}

void ChemData::SetColorIfHighlighted(QColor c) {
    for (Drawable *curr_draw : drawlist)
        curr_draw->SetColorIfHighlighted(c);
}

void ChemData::Move(double dx, double dy) {
    for (Drawable *curr_draw : drawlist)
        curr_draw->Move(dx, dy);
    notSaved = true;
}

void ChemData::Resize(DPoint *d1, double dy) {
    for (Drawable *curr_draw : drawlist)
        curr_draw->Resize(d1, dy);
    notSaved = true;
}

void ChemData::Rotate(DPoint *d1, double dy) {
    for (Drawable *curr_draw : drawlist)
        curr_draw->Rotate(d1, dy);
    notSaved = true;
}

void ChemData::Flip(DPoint *d1, int dy) {
    for (Drawable *curr_draw : drawlist)
        curr_draw->Flip(d1, dy);
    notSaved = true;
}

// Find minimum rectangle needed to enclose selection
QRect ChemData::selectionBox() {
    int top = 99999, bottom = 0, left = 99999, right = 0;
    QRect tmprect;

    for (Drawable *curr_draw : drawlist) {
        tmprect = curr_draw->BoundingBox();
        if (tmprect.isValid()) {
            left = std::min<int>(tmprect.left(), left);
            right = std::max<int>(tmprect.right(), right);
            top = std::min<int>(tmprect.top(), top);
            bottom = std::max<int>(tmprect.bottom(), bottom);
        }
    }

    left = std::max<int>(0, left - 3);
    top = std::max<int>(0, top - 3);

    // Change right and bottom to have a maximum of <pagesize>
    right += 5;
    bottom += 3;

    return QRect(QPoint(left, top), QPoint(right, bottom));
}

// when doing multiple selection via MODE_SELECT_MULTIPLE, we will
// have to highlight/unhighlight regions of the drawing as the selection
// box changes.  This function is called to start checking whether objects
// fall within the select box.
void ChemData::NewSelectRect(QRect n, bool shiftdown) {
    QList<DPoint *> allpts = UniquePoints();

    for (DPoint *curr_pt : allpts) {
        if (n.contains(curr_pt->toQPoint()) == true) {
            curr_pt->setHighlighted(true);
        } else {
            curr_pt->setHighlighted(false);
        }
    }

    for (Drawable *curr_draw : drawlist) {
        curr_draw->isWithinRect(n, shiftdown);
    }
}

// Get list of unique points contained in all Molecules.
QList<DPoint *> ChemData::UniquePoints() {
    QList<DPoint *> up, tp;

    for (Drawable *curr_draw : drawlist) {
        tp = curr_draw->AllPoints();
        for (DPoint *curr_pt : tp)
            up.append(curr_pt);
    }

    qDebug() << up.count();
    return up;
}

QList<Drawable *> ChemData::UniqueObjects() {
    QList<Drawable *> uo;

    for (Drawable *curr_draw : drawlist) {
        for (Drawable *td2 : curr_draw->AllObjects()) {
            uo.append(td2);
        }
    }

    qInfo() << uo.count() << " unique objects";
    return uo;
}

int ChemData::Size() { return drawlist.size(); }

// cmake#include "chemdata.moc"
