//  _
// | |_ ___  _   _ _ __ ___ _ __  _ __
// | __/ _ \| | | | '__/ __| '_ \| '_ \
// | || (_) | |_| | | | (__| |_) | |_) |
//  \__\___/ \__,_|_|(_)___| .__/| .__/
//                         |_|   |_|
// Author: Love Billenius <lovbi127@student.liu.se>

#include "Tour.h"
#include "Node.h"
#include "Point.h"
#include <iostream>

Tour::Tour() : m_startNode(nullptr) {}

Tour::~Tour() {
    if (m_startNode == nullptr)
        return;

    Node *node = m_startNode;
    if (node->next == m_startNode) {
        delete node;
        return;
    }

    do {
        Node *next = node->next;
        delete node;
        node = next;
    } while (node != m_startNode);
}

void Tour::show() const {
    if (m_startNode == nullptr)
        return;

    Node *node = m_startNode;
    do {
        Point &p = node->point;
        // I'm not using std::endl here to avoid flushing stdout for every
        // iteration.
        std::cout << '(' << p.x << ", " << p.y << ")\n";
        node = node->next;
    } while (node != m_startNode);

    // Finally, flush!
    std::flush(std::cout);
}

void Tour::draw(QGraphicsScene *scene) const {
    if (m_startNode == nullptr)
        return;

    Node *node = m_startNode;
    do{
        node->point.draw(scene);
        node->point.drawTo(node->next->point, scene);
        node = node->next;
    } while (node != m_startNode);
}

int Tour::size() const {
    if (m_startNode == nullptr)
        return 0;

    int count = 0;
    Node *node = m_startNode;
    do {
        count++;
        node = node->next;
    } while (node != m_startNode);

    return count;
}

double Tour::distance() const {
    if (m_startNode == nullptr)
        return 0;

    double totalDistance = 0;
    Node *node = m_startNode;
    do {
        totalDistance += node->point.distanceTo(node->next->point);
        node = node->next;
    } while (node != m_startNode);

    return totalDistance;
}

void Tour::insertNearest(Point p) {
    if (m_startNode == nullptr) {
        m_startNode = new Node(p);
        m_startNode->next = m_startNode; // Make it cirkular
        return;
    }
    if (m_startNode->next == nullptr) {
        auto newNode = new Node(p, m_startNode);
        m_startNode->next = newNode;
        return;
    }

    Node *nearest = m_startNode;
    double minDistance = Q_INFINITY;

    Node *node = m_startNode;
    do {
        double currentDistance = node->point.distanceTo(p);
        if (currentDistance < minDistance) {
            minDistance = currentDistance;
            nearest = node;
        }
        node = node->next;
    } while (node != m_startNode);

    auto *newNode = new Node(p, nearest->next);
    nearest->next = newNode;
}

void Tour::insertSmallest(Point p) {
    if (m_startNode == nullptr) {
        m_startNode = new Node(p);
        m_startNode->next = m_startNode; // Make it cirkular
        return;
    }
    if (m_startNode->next == nullptr) {
        auto newNode = new Node(p, m_startNode);
        m_startNode->next = newNode;
        return;
    }

    Node *bestNode = m_startNode;
    double minIncrease = Q_INFINITY;

    Node *node = m_startNode;
    do {
        double increase = node->point.distanceTo(p) +
                          p.distanceTo(node->next->point) -
                          node->point.distanceTo(node->next->point);
        if (increase < minIncrease) {
            minIncrease = increase;
            bestNode = node;
        }
        node = node->next;
    } while (node != m_startNode);

    auto *newNode = new Node(p, bestNode->next);
    bestNode->next = newNode;
}
