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

// bond.cpp - Bond's implementation of functions

#include "bond.h"
#include "bondedit.h"
#include "defs.h"
#include "drawable.h"
#include "render2d.h"

Bond::Bond(Render2D *r1, QObject *parent) : Drawable(parent) {
    r = r1;
    order = 1;
    stereo = STEREO_UNDEFINED;
    dashed = 0;
    highlighted = false;
    thick = 1;
    wside = 0;
    auto_wside = 1;
    IR_shift = -100.0;
    IR_intensity = "NA";
    cname = "";
    rxnlist = "Run 'Tools/Reaction/Reverse reactions' first.";
}

Bond *Bond::CloneTo(Drawable *target) const {
    if (!target)
        target = new Bond(r, parent());

    Bond *t = static_cast<Bond *>(this->Drawable::CloneTo(target));
    t->order = order;
    t->stereo = stereo;
    t->dashed = dashed;
    t->wside = wside;
    t->auto_wside = auto_wside;
    t->IR_shift = IR_shift;
    t->IR_intensity = IR_intensity;
    t->partial_charge = partial_charge;
    t->rxnlist = rxnlist;
    t->cname = cname;
    return t;
}

void Bond::setOrder(int a) {
    order = a;
    if (order == 5)
        stereo = STEREO_UP;
    if (order == 7)
        stereo = STEREO_DOWN;
    if (order == 8) {
        order = 7;
        // tmp_pt = start;
        // start = end;
        // end = tmp_pt;
        stereo = STEREO_DOWN;
    }
}

bool Bond::operator==(const Bond &other) const {
    return (((start == other.start) && (end == other.end)) ||
            ((end == other.start) && (start == other.end)));
}

bool Bond::isWithinRect(QRect n, bool /*shiftdown*/) {
    if (DPointInRect(start, n) && DPointInRect(end, n))
        highlighted = true;
    else
        highlighted = false;
    return highlighted;
}

void Bond::Edit() {
    int db1 = 0;

    qDebug() << "edit bond";
    BondEditDialog be(r, start, end, TYPE_BOND, order, dashed, thick, 0, color);

    if (auto_wside == 0)
        db1 = wside;
    be.setDoubleBond(db1);
    if (!be.exec())
        return;
    qDebug() << "change";
    color = be.Color();
    order = be.Order();
    dashed = be.Dash();
    thick = be.Thick();
    db1 = be.DoubleBond();
    if (db1 == 0) {
        auto_wside = 1;
    } else {
        auto_wside = 0;
        wside = db1;
    }
}

void Bond::Render() {
    // Red if highlighted
    QColor b_color = highlighted ? QColor(255, 0, 0) : color;

    switch (order) {
    // Single bonds
    case 1:
        r->drawLine(start->toQPointF(), end->toQPointF(), thick, b_color, dashed);
        break;

    // Double and Triple bonds
    case 3: {
        double spacing = preferences.getDoubleBondOffset() * Thick();
        QPointF startp = start->toQPointF();
        QPointF endp = end->toQPointF();
        QPointF vec = endp - startp;

        // Make orthogonal scaled vector
        QPointF ortho(-vec.y(), vec.x());
        ortho *= spacing / sqrt(vec.x() * vec.x() + vec.y() + vec.y());

        // Make copy of number of dashed because it's going to be modified
        int numdashed = dashed;
        r->drawLine(startp - ortho, startp - ortho + vec, thick, b_color, numdashed-- > 0);
        r->drawLine(startp, endp, thick, b_color, numdashed-- > 0);
        r->drawLine(startp + ortho, startp + ortho + vec, thick, b_color, numdashed-- > 0);
    }
    case 2:
        break;

    // Stereo Up Bonds
    case 5:
        r->drawUpLine(start->toQPointF(), end->toQPointF(), b_color);
        break;

    // Wavy Bonds
    case 6:
        r->drawWavyLine(start->toQPointF(), end->toQPointF(), b_color);
        break;

    // Stereo Down Bonds
    case 7:
        r->drawDownLine(start->toQPointF(), end->toQPointF(), b_color);
        break;
    }

    if (order < 2 || order > 3)
        return; // TODO FIXME

    // double myangle, x1, y1, x2, y2, rise, run, lx, ly, trim = 0.02;
    // for double and triple bonds
    double myangle = getAngle(start, end);
    myangle += 90.0;
    myangle = myangle * (M_PI / 180.0);
    // double offs = start->distanceTo(end) / 25.0;
    /*
       offs = preferences.getDoubleBondOffset();
       if (order == 3) {
       if (thick > 2) offs += 0.5;
       if (thick > 3) offs += 0.4;
       if (thick > 4) offs += 0.3;
       } else {
       if (thick > 2) offs += 0.3;
       if (thick > 4) offs += 0.3;
       }
       x2 = cos(myangle) * 3.0 * offs;
       y2 = sin(myangle) * 3.0 * offs;
       // x1, y1, 3 and 4 are for use with special case 1 below
       x1 = cos(myangle) * 2.5 * offs;
       y1 = sin(myangle) * 2.5 * offs;
     */

    /* original (does not use preferences!)
       double spacing = 1.0;
       spacing = (double)thick / 2.0 + 1.0;
     */

    // check for order 2 special case (center double bond)

    bool special_case_1 =
        order == 2 && (((start->element == "O") && (start->substituents == 2)) ||
                       ((end->element == "O") && (end->substituents == 2)) ||
                       ((start->element == "C") && (start->substituents == 2)) ||
                       ((end->element == "C") && (end->substituents == 2)) ||
                       ((start->element == "CH2") && (start->substituents == 2)) ||
                       ((end->element == "CH2") && (end->substituents == 2)) ||
                       ((start->element == "N") && (end->element == "N")) ||
                       (wside == 2)); // force centered double bond

    double spacing =
        (double)thick / 2.0 + preferences.getDoubleBondOffset() / (special_case_1 ? 4.0 : 2.0);

    double x2 = cos(myangle) * spacing;
    double y2 = sin(myangle) * spacing;

    // x1, y1, 3 and 4 are for use with special case 1 below
    double x1 = cos(myangle) * spacing;
    double y1 = sin(myangle) * spacing;

    QPointF sp3(start->x + x1, start->y + y1);
    QPointF ep3(end->x + x1, end->y + y1);
    QPointF sp4(start->x - x1, start->y - y1);
    QPointF ep4(end->x - x1, end->y - y1);

    // shorten lines by removing from each end
    // double b_length = start->distanceTo(end);
    double trim = 0.05; // Percentage of bond to trim
    // if ( b_length < 100.0 )
    //     trim = 0.03;
    // if ( b_length < 51.0 )
    //     trim = 0.05;
    // if ( b_length < 27.0 )
    //     trim = 0.07;

    // find sp1 and ep1
    double rise = (end->y + y2) - (start->y + y2);
    double run = (end->x + x2) - (start->x + x2);
    double lx = start->x + x2 + run * trim;
    double ly = start->y + y2 + rise * trim;
    QPointF sp1(lx, ly);

    lx = start->x + x2 + run * (1.0 - trim);
    ly = start->y + y2 + rise * (1.0 - trim);
    QPointF ep1(lx, ly);

    // find sp2 and ep2
    rise = (end->y - y2) - (start->y - y2);
    run = (end->x - x2) - (start->x - x2);
    lx = start->x - x2 + run * trim;
    ly = start->y - y2 + rise * trim;
    QPointF sp2(lx, ly);

    lx = start->x - x2 + run * (1.0 - trim);
    ly = start->y - y2 + rise * (1.0 - trim);
    QPointF ep2(lx, ly);

    if (special_case_1 == true) {
        if (dashed > 1)
            r->drawLine(sp3, ep3, thick, b_color, 1);
        else
            r->drawLine(sp3, ep3, thick, b_color);

        if (dashed > 0)
            r->drawLine(sp4, ep4, thick, b_color, 1);
        else
            r->drawLine(sp4, ep4, thick, b_color);
        return;
    }

    QPointF sp, ep;

    if (order == 2) {
        if (wside == 1) {
            sp = sp1;
            ep = ep1;
        } else {
            sp = sp2;
            ep = ep2;
        }
        // qDebug() << "wside:" << wside;
        if (dashed > 1)
            r->drawLine(start->toQPointF(), end->toQPointF(), thick, b_color, 1);
        else
            r->drawLine(start->toQPointF(), end->toQPointF(), thick, b_color);

        if (dashed > 0)
            r->drawLine(sp, ep, thick, b_color, 1);
        else
            r->drawLine(sp, ep, thick, b_color);
        return;
    }
}

int Bond::Type() { return TYPE_BOND; }

bool Bond::Find(DPoint *target) {
    if (start == target)
        return true;
    if (end == target)
        return true;
    return false;
}

// You *did* call Find() first to make sure target is in this Bond...?
DPoint *Bond::otherPoint(DPoint *target) {
    if (target == start)
        return end;
    if (target == end)
        return start;
    return 0;
}

DPoint *Bond::FindNearestPoint(DPoint *target, double &dist) {
    if (target->distanceTo(start) < target->distanceTo(end)) {
        dist = target->distanceTo(start);
        return start;
    } else {
        dist = target->distanceTo(end);
        return end;
    }
}

Drawable *Bond::FindNearestObject(DPoint *target, double &dist) {
    dist = DistanceToLine(start, end, target);
    return this;
}

void Bond::setPoints(DPoint *s, DPoint *e) {
    start = s;
    end = e;
}

QRect Bond::BoundingBox() {
    if (highlighted == false)
        return QRect(QPoint(999, 999), QPoint(0, 0));
    int top, bottom, left, right, swp;

    top = (int)start->y;
    left = (int)start->x;
    bottom = (int)end->y;
    right = (int)end->x;
    if (bottom < top) {
        swp = top;
        top = bottom;
        bottom = swp;
    }
    if (right < left) {
        swp = left;
        left = right;
        right = swp;
    }
    return QRect(QPoint(left, top), QPoint(right, bottom));
}

double Bond::Enthalpy() {
    double dh = 0.0;

    QString atom1, atom2, swp;

    atom1 = start->element;
    atom2 = end->element;
    if (QString::compare(atom1, atom2) > 0) {
        swp = atom1;
        atom1 = atom2;
        atom2 = swp;
    }
    // special cases (functional groups, etc.)
    if (atom1 == "OH") {
        dh += 463.0;
        atom1 = "O";
    }
    if (atom2 == "OH") {
        dh += 463.0;
        atom2 = "O";
    }

    if ((order == 1) || (order == 5) || (order == 7)) {
        if ((atom1 == "Br") && (atom2 == "H"))
            dh = 366.0;
        if ((atom1 == "C") && (atom2 == "Br"))
            dh = 276.0;
        if ((atom1 == "C") && (atom2 == "C"))
            dh = 348.0;
        if ((atom1 == "C") && (atom2 == "Cl"))
            dh = 328.0;
        if ((atom1 == "C") && (atom2 == "F"))
            dh = 441.0;
        if ((atom1 == "C") && (atom2 == "H"))
            dh = 413.0;
        if ((atom1 == "C") && (atom2 == "I"))
            dh = 240.0;
        if ((atom1 == "C") && (atom2 == "N"))
            dh = 292.0;
        if ((atom1 == "C") && (atom2 == "O"))
            dh = 351.0;
        if ((atom1 == "C") && (atom2 == "S"))
            dh = 259.0;
        if ((atom1 == "Cl") && (atom2 == "H"))
            dh = 432.0;
        if ((atom1 == "Cl") && (atom2 == "N"))
            dh = 200.0;
        if ((atom1 == "Cl") && (atom2 == "O"))
            dh = 203.0;
        if ((atom1 == "F") && (atom2 == "H"))
            dh = 563.0;
        if ((atom1 == "F") && (atom2 == "N"))
            dh = 270.0;
        if ((atom1 == "F") && (atom2 == "O"))
            dh = 185.0;
        if ((atom1 == "H") && (atom2 == "H"))
            dh = 436.0;
        if ((atom1 == "H") && (atom2 == "I"))
            dh = 299.0;
        if ((atom1 == "H") && (atom2 == "N"))
            dh = 391.0;
        if ((atom1 == "H") && (atom2 == "O"))
            dh = 463.0;
        if ((atom1 == "H") && (atom2 == "S"))
            dh = 339.0;
        if ((atom1 == "O") && (atom2 == "O"))
            dh = 139.0;
    }
    if (order == 2) {
        if ((atom1 == "C") && (atom2 == "C"))
            dh = 615.0;
        if ((atom1 == "C") && (atom2 == "N"))
            dh = 615.0;
        if ((atom1 == "C") && (atom2 == "O"))
            dh = 728.0;
        if ((atom1 == "C") && (atom2 == "S"))
            dh = 477.0;
        if ((atom1 == "N") && (atom2 == "N"))
            dh = 418.0;
        if ((atom1 == "O") && (atom2 == "O"))
            dh = 498.0;
    }
    if (order == 3) {
        if ((atom1 == "C") && (atom2 == "C"))
            dh = 812.0;
        if ((atom1 == "C") && (atom2 == "N"))
            dh = 891.0;
        if ((atom1 == "N") && (atom2 == "N"))
            dh = 945.0;
    }

    return dh;
}

double Bond::Length() { return start->distanceTo(end); }
