/**********************************************************************************************
    Copyright (C) 2014 Oliver Eichler <oliver.eichler@gmx.de>

    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 <http://www.gnu.org/licenses/>.

**********************************************************************************************/

#include "canvas/IDrawObject.h"

#include <QtWidgets>

#include "canvas/IDrawContext.h"
#include "units/IUnit.h"

IDrawObject::IDrawObject(QObject* parent) : QObject(parent) {}

IDrawObject::~IDrawObject() {}

void IDrawObject::saveConfig(QSettings& cfg) {
  cfg.setValue("opacity", getOpacity());
  cfg.setValue("minScale", getMinScale());
  cfg.setValue("maxScale", getMaxScale());
}

void IDrawObject::loadConfig(QSettings& cfg) {
  slotSetOpacity(cfg.value("opacity", getOpacity()).toDouble());
  setMinScale(cfg.value("minScale", getMinScale()).toDouble());
  setMaxScale(cfg.value("maxScale", getMaxScale()).toDouble());

  emit sigPropertiesChanged();
}

bool IDrawObject::isOutOfScale(const QPointF& scale) const {
  if ((getMinScale() != NOFLOAT) && (scale.x() < getMinScale())) {
    return true;
  }
  if ((getMaxScale() != NOFLOAT) && (scale.x() > getMaxScale())) {
    return true;
  }

  return false;
}

void IDrawObject::getLayers(QListWidget& list) { list.clear(); }

void IDrawObject::setMinScale(qreal s) {
  if ((s != NOFLOAT) && (maxScale != NOFLOAT)) {
    if (s > maxScale) {
      return;
    }
  }
  minScale = s;
}

void IDrawObject::setMaxScale(qreal s) {
  if ((s != NOFLOAT) && (minScale != NOFLOAT)) {
    if (s < minScale) {
      return;
    }
  }
  maxScale = s;
}

void IDrawObject::drawTileLQ(const QImage& img, QPolygonF& l, QPainter& p, IDrawContext& context,
                             const CProj& proj) const {
  QPolygonF tmp = l;
  context.convertRad2Px(l);

  // adjust the tiles width and height to fit the buffer's scale
  qreal dx1 = l[0].x() - l[1].x();
  qreal dy1 = l[0].y() - l[1].y();
  qreal dx2 = l[0].x() - l[3].x();
  qreal dy2 = l[0].y() - l[3].y();
  qreal w = qCeil(qSqrt(dx1 * dx1 + dy1 * dy1));
  qreal h = qCeil(qSqrt(dx2 * dx2 + dy2 * dy2));

  // switch to HQ if the gaps get visible
  if ((context.getZoomFactor().x() > 70) && proj.isValid()) {
    if ((qAbs(dy1) > 2) || (qAbs(dx2) > 2)) {
      drawTileHQ(img, tmp, p, context, proj);
      return;
    }
  }

  // calculate rotation. This is not really a reprojection but might be good enough for close zoom levels
  qreal a = qAtan(dy1 / dx1) * RAD_TO_DEG;

  // finally translate, scale, rotate and draw tile
  p.save();
  p.translate(l[0]);
  auto imgSize = img.deviceIndependentSize();
  p.scale(w / imgSize.width(), h / imgSize.height());
  p.rotate(a);
  p.drawImage(0, 0, img);
  p.restore();
}

void IDrawObject::drawTileHQ(const QImage& img, QPolygonF& l, QPainter& p, IDrawContext& context,
                             const CProj& proj) const {
  auto imgSize = img.deviceIndependentSize();
  // the sub-tiles need a sensible size
  // if they get too small there will be too much
  // rounding effects.
  qint32 nStepsX = 8;
  qint32 nStepsY = 8;
  if (imgSize.width() / nStepsX < 32) {
    nStepsX = 4;
  }
  if (imgSize.height() / nStepsY < 32) {
    nStepsY = 4;
  }

  // transform the rad coordinates from l into the coord. system
  // of the map
  proj.transform(l, PJ_INV);

  // calculate nStepsX*nStepsY squares evenly distributed over the tile
  // in map coords
  qreal subStepX = (l[1].x() - l[0].x()) / nStepsX;
  qreal subStepY = (l[3].y() - l[0].y()) / nStepsY;
  qreal offsetX = l[0].x();
  qreal offsetY = l[0].y();

  QPolygonF quads(nStepsX * nStepsY * 4);
  QPointF* pPt = quads.data();

  for (int y = 0; y < nStepsY; ++y) {
    for (int x = 0; x < nStepsX; ++x) {
      pPt->rx() = offsetX;
      pPt->ry() = offsetY;
      ++pPt;
      pPt->rx() = offsetX + subStepX;
      pPt->ry() = offsetY;
      ++pPt;
      pPt->rx() = offsetX + subStepX;
      pPt->ry() = offsetY + subStepY;
      ++pPt;
      pPt->rx() = offsetX;
      pPt->ry() = offsetY + subStepY;
      ++pPt;

      offsetX += subStepX;
    }

    offsetX = l[0].x();
    offsetY += subStepY;
  }

  // transform the squares back to lon/lat coords in rad
  proj.transform(quads, PJ_FWD);
  // convert the lon/lat coords of the squares into pixel coords of the
  // canvas using the view's projection
  context.convertRad2Px(quads);

  QRectF rect(0, 0, imgSize.width() / nStepsX, imgSize.height() / nStepsY);
  const qreal rw = rect.width();
  const qreal rh = rect.height();

  // iterate over all squares, calculate the translation and rotation and draw the part of the
  // tile matching the square.
  pPt = quads.data();
  for (int y = 0; y < nStepsY; ++y) {
    rect.moveTop(y * rh);
    for (int x = 0; x < nStepsX; ++x, pPt += 4) {
      // adjust the tiles width and height to fit the buffer's scale
      qreal dx1 = pPt[0].x() - pPt[1].x();
      qreal dy1 = pPt[0].y() - pPt[1].y();
      qreal dx2 = pPt[0].x() - pPt[3].x();
      qreal dy2 = pPt[0].y() - pPt[3].y();
      qreal w = /*qRound*/ (qSqrt(dx1 * dx1 + dy1 * dy1));
      qreal h = /*qRound*/ (qSqrt(dx2 * dx2 + dy2 * dy2));

      // calculate rotation. This is not really a reprojection but might be good enough for close zoom levels
      qreal a = qAtan(dy1 / dx1) * RAD_TO_DEG;

      // move rect to select the part of the tile to draw.
      rect.moveLeft(x * rw);

      // finally translate, scale, rotate and draw tile
      p.save();
      p.translate(pPt[0]);
      p.scale(w / rw, h / rh);
      p.rotate(a);
      p.drawImage(QPoint(0, 0), img, rect);
      p.restore();
    }
  }
}
