/*
 * I love Qt
 */
#include "cell.h"
#include "units/unit.h"
#include "hotseatgame/gameproperties.h"
#include <string>
#include <queue>
#include <vector>
#include <map>
#include <iostream>
#include <cmath>

#include <QPainter>
#include <QPolygon>
#include <QDebug>

class EffectsForCell{
public:
	void OperateOnCell(Cell*){}
};


Cell::Cell(QGraphicsItem* parent) : QGraphicsItem(parent)
{
    leftUp_ = left_ = leftDown_ = nullptr;
    rightUp_ = right_ = rightDown_ = nullptr;
    clearCell_();
    AddedToQuery_ = true;
    col_ = row_ = 0;
    hovered_ = false;
    coor_x_ = coor_y_ = -1;
    setAcceptHoverEvents(true);
}

Cell * Cell::getleftUp() {
	return leftUp_;
}
void Cell::setleftUp(Cell * t) {
	leftUp_ = t;
}
Cell * Cell::getleft() {
	return left_;
}
void Cell::setleft(Cell * t) {
	left_ = t;
}
Cell * Cell::getleftDown() {
	return leftDown_;
}
void Cell::setleftDown(Cell * t) {
	leftDown_ = t;
}
Cell * Cell::getrightUp() {
	return rightUp_;
}
void Cell::setrightUp(Cell * t) {
	rightUp_ = t;
}
Cell * Cell::getright() {
	return right_;
}
void Cell::setright(Cell * t) {
	right_ = t;
}
Cell * Cell::getrightDown() {
	return rightDown_;
}
void Cell::setrightDown(Cell * t) {
	rightDown_ = t;
}
std::shared_ptr<Unit> Cell::getCharacter() {
	return character_;
}
void Cell::setCharacter(std::shared_ptr<Unit> t) {
    character_ = t;
}

double Cell::getXCoordinate()
{
    return coor_x_;
}

void Cell::setXCoordinate(double coordinate)
{
    coor_x_ = coordinate;
}

double Cell::getYCoordinate() {
    return coor_y_;
}

void Cell::setYCoordinate(double coordinate) {
    coor_y_ = coordinate;
}

int Cell::getdistance_barrier(){
    return distance_barrier_;
}
void Cell::setdistance_barrier(int distance_barrier){
    distance_barrier_ = distance_barrier;
}
int Cell::getdistance_through(){
    return distance_through_;
}
void Cell::setdistance_through(int distance_through){
    distance_through_ = distance_through;
}

bool Cell::getisMoveAble(){
    return isMoveAble_;
}
void Cell::setisMoveAble(bool isMoveAble){
    isMoveAble_ = isMoveAble;
}
bool Cell::getisMeleeAttackAble(){
    return isMeleeAttackAble_;
}
void Cell::setisMeleeAttackAble(bool isMeleeAttackAble){
    isMeleeAttackAble_ = isMeleeAttackAble;
}
bool Cell::getisRangeAttackAble(){
    return isRangeAttackAble_;
}
void Cell::setisRangeAttackAble(bool isRangeAttackAble){
    isRangeAttackAble_ = isRangeAttackAble;
}

bool Cell::isEmpty() {
	return character_ == nullptr;
}

void Cell::recalculateAllEffectsList(){
    for(std::vector<EffectsForCell*>::iterator it = beginIteratorEffectsList();
            it != endIteratorEffectsList();++it){
        (*it)->OperateOnCell(this);
    }
}
void Cell::add(EffectsForCell* effect){
    if(effect == nullptr)
        throw new int(228);
    effects_list_.push_back(effect);
}
void Cell::remove(std::vector<EffectsForCell*>::iterator it){
    if(beginIteratorEffectsList() <= it && it < endIteratorEffectsList()){
        effects_list_.erase(it);
    }
}
void Cell::remove(EffectsForCell* effect){
    for(std::vector<EffectsForCell*>::iterator it = beginIteratorEffectsList();
            it != endIteratorEffectsList();++it){
        if((*it) == effect){
            remove(it);
            return;
        }
    }
}
std::vector<EffectsForCell*>::iterator Cell::beginIteratorEffectsList(){
    return effects_list_.begin();
}
std::vector<EffectsForCell*>::iterator Cell::endIteratorEffectsList(){
    return effects_list_.end();
}

void Cell::RecalculateTableWithCenterThisPoint() {
    clearTable_();
    std::queue<Cell*> qWithUnMoveable;
    updateMoveableCells_(qWithUnMoveable);
    updateUnMovealeCells_(qWithUnMoveable);
}
std::vector <Cell*> Cell::actualPath(Cell* to) {
    if (!to || !to->getisMoveAble())return std::vector<Cell*>();
    auto ret = std::vector<Cell*>(1, to);
    while (to != this) {
        Cell * parent = nullptr;
        auto f = [&parent](Cell * TestParent, Cell * Now) {
            if (TestParent && TestParent->getdistance_barrier() + 1 == Now->getdistance_barrier()
                && TestParent->getisMoveAble()) {
                parent = TestParent;
            }
        };
        if(parent == nullptr) throw std::string("Don`t recalculated");
        f(to->getleftUp(), to);
        f(to->getleft(), to);
        f(to->getleftDown(), to);
        f(to->getrightUp(), to);
        f(to->getright(), to);
        f(to->getrightDown(), to);
        to = parent;
        ret.push_back(to);
    }
    return ret;
}

void Cell::clearCell_() {
    setdistance_barrier(-1);
    setdistance_through(-1);
    setisMeleeAttackAble(false);
    setisMoveAble(false);
    setisRangeAttackAble(false);
}

QRectF Cell::boundingRect() const
{
    return QRectF(polygon.boundingRect());
}

void Cell::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
    polygon.clear();
    auto points = getPoints(GameProperties::CELL_MAX_RADIUS, 0);

    for (auto point : points) {
        polygon << QPoint(point.first, point.second);
    }
    polygon << QPoint(points[0].first, points[0].second);

    QPen pen = QPen(Qt::green, 2);
    painter->setPen(pen);
    painter->setBrush(Qt::green); // We set the brush, which will render the object
    painter->drawPolyline(polygon);  // Draw a triangle on a polygonal model

    if (hovered_) {
        painter->setOpacity(0.4);
        painter->drawPolygon(polygon);
    }
}

bool Cell::contains(const QPointF &point) const
{
    return polygon.contains(point.toPoint());
}

bool Cell::isHovered()
{
    return hovered_;
}

void Cell::setHovered(bool hovered)
{
    hovered_ = hovered;
}

void Cell::hoverEnterEvent(QGraphicsSceneHoverEvent *)
{
    hovered_ = true;
    update();
}

void Cell::hoverLeaveEvent(QGraphicsSceneHoverEvent *)
{
    hovered_ = false;
    update();
}

void Cell::clearTable_() {
	std::queue<Cell*> q;
	q.push(this);
	clearCell_();
	setdistance_through(0);
	this->AddedToQuery_ = false;
	auto f = [&q](Cell * t, Cell * parent, int delta_col, int delta_row) {
		if (t && t->AddedToQuery_ == true) {
			q.push(t);
			t->col_ = parent->col_ + delta_col;
			t->row_ = parent->row_ + delta_row;
			t->setdistance_through(
			        parent->getdistance_through() + 1
            );
			t->AddedToQuery_ = false;
		}
	};
	while (!q.empty()) {
		Cell * Now = q.front();
		q.pop();
		Now->clearCell_();
		f(Now->getleftUp(), Now, -1, 0);
		f(Now->getleft(), Now, 0, -1);
		f(Now->getleftDown(), Now, 1, -1);
		f(Now->getrightUp(), Now, -1, 1);
		f(Now->getright(), Now, 0, 1);
		f(Now->getrightDown(), Now, 1, 0);
	}
}

void Cell::recalcAttackable_(Cell *, bool){
//    if(Now == nullptr)return;
//    if (!isEmpty() && !Now->isEmpty() &&
//        getCharacter()->canAttackForDistance("Melee", Now->getdistance_barrier())
//            ) {
//        Now->setisMeleeAttackAble(isReached);
//    }
//    if (!isEmpty() && !Now->isEmpty() &&
//        getCharacter()->canAttackForDistance("Range", Now->getdistance_barrier())
//            ) {
//        Now->setisRangeAttackAble(true);
//    }
//    return;
}

void Cell::recalcMoveable_(Cell * Now, bool isReached){
    if(Now == nullptr || Now->isEmpty() || isEmpty())return;
    Now->setisMoveAble(isReached);
}

void Cell::updateMoveableCells_(std::queue<Cell*> & Q) {
	std::queue<Cell*> q;
	q.push(this);
	setdistance_barrier(0);
	auto f = [&q, &Q](Cell * t, Cell * parent) {
		if (t && !t->AddedToQuery_) {
			t->AddedToQuery_ = true;
			if (t->getCharacter() != NULL) {
				Q.push(t);
				return;
			}
			q.push(t);
			t->setdistance_barrier(
			        parent->getdistance_barrier() + 1
            );
		}
	};
	while (!q.empty()) {
		Cell * Now = q.front();
		q.pop();
		recalcMoveable_(Now, true);
		recalcAttackable_(Now, true);

		f(Now->getleftUp(), Now);
		f(Now->getleft(), Now);
		f(Now->getleftDown(), Now);
		f(Now->getrightUp(), Now);
		f(Now->getright(), Now);
		f(Now->getrightDown(), Now);
	}
}

void Cell::updateUnMovealeCells_(std::queue<Cell*> & Q) {
	auto f = [&Q](Cell * t, Cell * parent) {
		if (t && !t->AddedToQuery_) {
            parent->getCharacter();
			t->AddedToQuery_ = true;
			Q.push(t);
		}
	};
	while (!Q.empty()) {
		Cell * Now = Q.front();
		recalcMoveable_(Now, false);
		recalcAttackable_(Now, false);
		Q.pop();
		f(Now->getleftUp(), Now);
		f(Now->getleft(), Now);
		f(Now->getleftDown(), Now);
		f(Now->getrightUp(), Now);
		f(Now->getright(), Now);
		f(Now->getrightDown(), Now);
    }
}


Cell* Cell::getRealShootTarget(Cell* next){
	if(next == this){
		return next;
	}
	int next_col = next->col_;
	int next_row = next->row_;
	if(next_col - col_ == next_row - row_){//GoMainDiagonal
		if(next_col > col_){
			return getrightDown()->getright();
		}
		if(next_col < col_){
			return getleftUp()->getleft();
		}
		throw std::string("Don`t recalculated");
	}
	if(-2 * (next_col - col_) == (next_row - row_)){//GoSecondaryDiagonal
		if(next_col < col_){
			return getrightUp()->getright();
		}
		if(next_col > col_){
			return getleftDown()->getleft();
		}
		throw std::string("Don`t recalculated");
	}
	if(next_col - col_ == -2 * (next_row - row_)){//GoUp
		if(next_col < col_){
			return (getleftUp() != nullptr ? getleftUp()->getrightUp() : getrightUp()->getleftUp());
		}
		if(next_col > col_){
			return (getleftDown() != nullptr ? getleftDown()->getrightDown() : getrightDown()->getleftDown());
		}
		throw std::string("Don`t recalculated");
	}
	int row_secondary_diag = row_ - 2 * (next_col - col_);
	int row_main_diag = row_ + next_col - col_;
	int twice_row_up_line = 2 * row_ + col_ - next_col;
	if(next_col > col_){
		if(next_row < row_main_diag)return getleft();
		if(2 * next_row < twice_row_up_line)return getleftUp();
		if(next_row < row_secondary_diag)return getrightUp();
		return getright();
	}
	else{
		if(next_row < row_secondary_diag)return getleft();
		if(2 * next_row < twice_row_up_line)return getleftDown();
		if(next_row < row_main_diag)return getrightDown();
		return getright();
	}
}

std::vector<std::pair<int, int> > Cell::getPoints(double radius, double start_angle) {
    const double delta_radians = M_PI / 180.0;
    std::vector<std::pair<int, int> > res;
    std::vector<double> angles = {0, 60, 120, 180, 240, 300};
    for(size_t i = 0; i < angles.size(); ++i){
        angles[i] += start_angle;
        res.push_back({std::round(radius * std::sin(angles[i] * delta_radians)),
                       std::round(radius * std::cos(angles[i] * delta_radians))});
    }
    return res;
}

