#include <iostream>
#include <math.h>
#include <qwidget.h>
#include <qpainter.h>
#include <qprinter.h>
#include <qpaintdevicemetrics.h>
#include <qpointarray.h>
#include <qrect.h>
#include <qpixmap.h>
#include <kprinter.h>

#include "paintable.h"
#include "render2d.h"
#include "chemdata.h"
#include "defs.h"

void Render2D::PrintSetup() {
  printer = new KPrinter;
  // the following statement is needed for Qt 3.0 or higher
#if QT_VERSION >= 300
  printer->setResolution(100);  // for convenient calculation of line length
#endif
  printer->setFullPage(true);
  printer->setPageSize(KPrinter::Letter);
  printer->setOrientation(KPrinter::Portrait);
  printer->setColorMode(KPrinter::GrayScale);
}

void Render2D::UpdatePageGeometry() {
  if (page_orientation == PAGE_PORTRAIT) {
    printer->setOrientation(KPrinter::Portrait);
    switch (page_size) {
    case PAGE_LETTER:
      printer->setPageSize(KPrinter::Letter);
      renderHeight = 1000;
      renderWidth = 750;
      break;
    case PAGE_LEGAL:
      printer->setPageSize(KPrinter::Legal);
      renderHeight = 1300;
      renderWidth = 750;
      break;
    case PAGE_A4:
      printer->setPageSize(KPrinter::A4);
      renderHeight = 1070;
      renderWidth = 776;
      break;
    case PAGE_640:
      renderWidth = 640;
      renderWidth = 480;
      break;
    case PAGE_800:
      renderWidth = 800;
      renderWidth = 600;
      break;
    case PAGE_1024:
      renderWidth = 1024;
      renderWidth = 768;
      break;
    default:
      break;
    }
  } else { // page_orientation == PAGE_LANDSCAPE
    printer->setOrientation(KPrinter::Landscape);
    switch (page_size) {
    case PAGE_LETTER:
      printer->setPageSize(KPrinter::Letter);
      renderHeight = 750;
      renderWidth = 1000;
      break;
    case PAGE_LEGAL:
      printer->setPageSize(KPrinter::Legal);
      renderHeight = 750;
      renderWidth = 1300;
      break;
    case PAGE_A4:
      printer->setPageSize(KPrinter::A4);
      renderHeight = 776;
      renderWidth = 1070;
      break;
    case PAGE_640:
      renderWidth = 640;
      renderWidth = 480;
      break;
    case PAGE_800:
      renderWidth = 800;
      renderWidth = 600;
      break;
    case PAGE_1024:
      renderWidth = 1024;
      renderWidth = 768;
      break;
    default:
      break;
    }
  }
  resize(renderWidth, renderHeight);
}

void Render2D::Print() {
  if (!printer->setup(this)) {
    emit SignalSetStatusBar( QString("Printing canceled.") );
    return;
  }

  //QPaintDeviceMetrics pm(printer);

  c->DeselectAll();
  // set output device
  outputDevice = OUTPUT_PRINTER;
  // render all objects
  paintqueue.clear();
  repaint();
  QPainter p(printer);
  Paintable *tmp_paint;
  for (tmp_paint = paintqueue.first(); tmp_paint != NULL;
       tmp_paint = paintqueue.next()) {
    QPoint a = tmp_paint->a;
    QPoint b = tmp_paint->b;
    QColor c1 = tmp_paint->c;
    if (tmp_paint->op == OP_LINE) {
      if (tmp_paint->s == 0)
	p.setPen(tmp_paint->c);
      if (tmp_paint->s == 1)  // dashed line
	p.setPen(QPen(tmp_paint->c,1,DotLine));
      p.drawLine(tmp_paint->a, tmp_paint->b);
    }
    if (tmp_paint->op == OP_FILLBOX) {
      p.setPen(c1);
      p.fillRect(QRect(a, b), c1);
    }
    if (tmp_paint->op == OP_UP_LINE) {
      p.setPen(c1);
      p.setBrush(QBrush(c1));
      QPointArray triangle(3);
      triangle.setPoint(0, a);
      triangle.setPoint(1, b);
      triangle.setPoint(2, tmp_paint->b1);
      p.drawPolygon(triangle);
    }
    if (tmp_paint->op == OP_DASH_LINE) {
      p.setPen(QPen(tmp_paint->c,0,DotLine));
      p.drawLine(tmp_paint->a, tmp_paint->b);
    }
    if (tmp_paint->op == OP_TEXT) {
      p.setPen(tmp_paint->c);
      p.setFont(tmp_paint->f);
      p.drawText(tmp_paint->a, tmp_paint->ch);
    }
    if (tmp_paint->op == OP_CURVE_CW180) {
      // calculate curve
      p.setPen(tmp_paint->c);
      QPoint ce = Midpoint(tmp_paint->a, tmp_paint->b);
      int d = RoundOff(DistanceBetween(tmp_paint->a, ce));
      double sa = getAngle(ce, tmp_paint->a);
      QPointArray pa;
      pa.makeArc(ce.x() - d, ce.y() - d, 2*d, 2*d, -sa*16, -2880);
      p.drawPolyline(pa);
      // calculate arrowhead
      // if curve too small, don't draw arrowhead
      if (pa.count() == 0) return;
      QPoint realb(pa.at(pa.count() - 1));
      sa = getAngle(tmp_paint->b, tmp_paint->a);
      double newang1 = sa + 60.0;
      double newang2 = sa + 120.0;
      QPoint a1(realb.x() + (cos(newang1/MOL_ARAD) * 10.0),
		realb.y() + (sin(newang1/MOL_ARAD) * 10.0));
      QPoint a2(realb.x() + (cos(newang2/MOL_ARAD) * 10.0),
		realb.y() + (sin(newang2/MOL_ARAD) * 10.0));
      p.drawLine(realb, a1);
      p.drawLine(realb, a2);
    }
    if (tmp_paint->op == OP_CURVE_CCW180) {
      // calculate curve
      p.setPen(tmp_paint->c);
      QPoint ce = Midpoint(tmp_paint->a, tmp_paint->b);
      int d = RoundOff(DistanceBetween(tmp_paint->a, ce));
      double sa = getAngle(ce, tmp_paint->a);
      QPointArray pa;
      pa.makeArc(ce.x() - d, ce.y() - d, 2*d, 2*d, -sa*16, 2880);
      p.drawPolyline(pa);
      // calculate arrowhead
      // if curve too small, don't draw arrowhead
      if (pa.count() == 0) return;
      QPoint realb(pa.at(pa.count() - 1));
      sa = getAngle(tmp_paint->b, tmp_paint->a);
      double newang1 = sa + 60.0;
      double newang2 = sa + 120.0;
      QPoint a1(realb.x() + (cos(newang1/MOL_ARAD) * 10.0),
		realb.y() + (sin(newang1/MOL_ARAD) * 10.0));
      QPoint a2(realb.x() + (cos(newang2/MOL_ARAD) * 10.0),
		realb.y() + (sin(newang2/MOL_ARAD) * 10.0));
      p.drawLine(realb, a1);
      p.drawLine(realb, a2);
    }
    if (tmp_paint->op == OP_PIXMAP) {
      p.drawPixmap(tmp_paint->a, tmp_paint->p);
    }
    if (tmp_paint->op == OP_CURVE_CW90) {
      // first, figure out where middle of circle is
      double d1 = DistanceBetween(a, b);
      double ia = getAngle(a, b);
      d1 = d1 / 1.4142136;
      ia = 45.0;
      double dx1 = ( (double)b.x() - (double)a.x() ) / 1.4142136;
      double dy1 = ( (double)b.y() - (double)a.y() ) / 1.4142136;
      // rotate vector (dx1, dy1) 45 degrees clockwise
      double ia_rad = ia * M_PI / 180.0;
      double dx2 = dx1*cos(ia_rad) - dy1*sin(ia_rad);
      double dy2 = dx1*sin(ia_rad) + dy1*cos(ia_rad);
      QPoint ce(a.x() + RoundOff(dx2), a.y() + RoundOff(dy2));
      double sa = getAngle(ce, a);
      QPointArray pa;
      int d = RoundOff(d1);
      pa.makeArc(ce.x() - d, ce.y() - d, 2*d, 2*d, -sa*16, -1440);
      drawPolyline(pa, c1);    
      // calculate arrowhead
      // if curve too small, don't draw arrowhead
      if (pa.count() == 0) return;
      QPoint realb(pa.at(pa.count() - 1));
      sa = getAngle(b, a);
      double newang1 = sa + 15.0;
      double newang2 = sa + 75.0;
      QPoint a1(realb.x() + (cos(newang1/MOL_ARAD) * 10.0),
		realb.y() + (sin(newang1/MOL_ARAD) * 10.0));
      QPoint a2(realb.x() + (cos(newang2/MOL_ARAD) * 10.0),
		realb.y() + (sin(newang2/MOL_ARAD) * 10.0));
      drawLine(realb, a1, 1, c1);
      drawLine(realb, a2, 1, c1);
    }
    if (tmp_paint->op == OP_CURVE_CCW90) {
      // first, figure out where middle of circle is
      double d1 = DistanceBetween(a, b);
      double ia = getAngle(a, b);
      d1 = d1 / 1.4142136;
      ia = -45.0;
      double dx1 = ( (double)b.x() - (double)a.x() ) / 1.4142136;
      double dy1 = ( (double)b.y() - (double)a.y() ) / 1.4142136;
      // rotate vector (dx1, dy1) 45 degrees clockwise
      double ia_rad = ia * M_PI / 180.0;
      double dx2 = dx1*cos(ia_rad) - dy1*sin(ia_rad);
      double dy2 = dx1*sin(ia_rad) + dy1*cos(ia_rad);
      QPoint ce(a.x() + RoundOff(dx2), a.y() + RoundOff(dy2));
      double sa = getAngle(ce, a);
      QPointArray pa;
      int d = RoundOff(d1);
      pa.makeArc(ce.x() - d, ce.y() - d, 2*d, 2*d, -sa*16, 1440);
      drawPolyline(pa, c1);    
      // calculate arrowhead
      // if curve too small, don't draw arrowhead
      if (pa.count() == 0) return;
      QPoint realb(pa.at(pa.count() - 1));
      sa = getAngle(b, a);
      double newang1 = sa - 15.0;
      double newang2 = sa - 75.0;
      QPoint a1(realb.x() + (cos(newang1/MOL_ARAD) * 10.0),
		realb.y() + (sin(newang1/MOL_ARAD) * 10.0));
      QPoint a2(realb.x() + (cos(newang2/MOL_ARAD) * 10.0),
		realb.y() + (sin(newang2/MOL_ARAD) * 10.0));
      drawLine(realb, a1, 1, c1);
      drawLine(realb, a2, 1, c1);
    }    
    if (tmp_paint->op == OP_CURVE_CW270) {
      // first, figure out where middle of circle is
      double d1 = DistanceBetween(a, b);
      double ia = getAngle(a, b);
      d1 = d1 / 1.4142136;
      ia = -45.0;
      double dx1 = ( (double)b.x() - (double)a.x() ) / 1.4142136;
      double dy1 = ( (double)b.y() - (double)a.y() ) / 1.4142136;
      // rotate vector (dx1, dy1) 45 degrees clockwise
      double ia_rad = ia * M_PI / 180.0;
      double dx2 = dx1*cos(ia_rad) - dy1*sin(ia_rad);
      double dy2 = dx1*sin(ia_rad) + dy1*cos(ia_rad);
      QPoint ce(a.x() + RoundOff(dx2), a.y() + RoundOff(dy2));
      double sa = getAngle(ce, a);
      QPointArray pa;
      int d = RoundOff(d1);
      pa.makeArc(ce.x() - d, ce.y() - d, 2*d, 2*d, -sa*16, -4320);
      drawPolyline(pa, c1);    
      // calculate arrowhead
      // if curve too small, don't draw arrowhead
      if (pa.count() == 0) return;
      QPoint realb(pa.at(pa.count() - 1));
      sa = getAngle(b, a);
      double newang1 = sa + 165.0;
      double newang2 = sa + 105.0;
      QPoint a1(realb.x() + (cos(newang1/MOL_ARAD) * 10.0),
		realb.y() + (sin(newang1/MOL_ARAD) * 10.0));
      QPoint a2(realb.x() + (cos(newang2/MOL_ARAD) * 10.0),
		realb.y() + (sin(newang2/MOL_ARAD) * 10.0));
      drawLine(realb, a1, 1, c1);
      drawLine(realb, a2, 1, c1);
    }
    if (tmp_paint->op == OP_CURVE_CCW270) {
      // first, figure out where middle of circle is
      double d1 = DistanceBetween(a, b);
      double ia = getAngle(a, b);
      d1 = d1 / 1.4142136;
      ia = 45.0;
      double dx1 = ( (double)b.x() - (double)a.x() ) / 1.4142136;
      double dy1 = ( (double)b.y() - (double)a.y() ) / 1.4142136;
      // rotate vector (dx1, dy1) 45 degrees clockwise
      double ia_rad = ia * M_PI / 180.0;
      double dx2 = dx1*cos(ia_rad) - dy1*sin(ia_rad);
      double dy2 = dx1*sin(ia_rad) + dy1*cos(ia_rad);
      QPoint ce(a.x() + RoundOff(dx2), a.y() + RoundOff(dy2));
      double sa = getAngle(ce, a);
      QPointArray pa;
      int d = RoundOff(d1);
      pa.makeArc(ce.x() - d, ce.y() - d, 2*d, 2*d, -sa*16, 4320);
      drawPolyline(pa, c1);    
      // calculate arrowhead
      // if curve too small, don't draw arrowhead
      if (pa.count() == 0) return;
      QPoint realb(pa.at(pa.count() - 1));
      sa = getAngle(b, a);
      double newang1 = sa - 165.0;
      double newang2 = sa - 105.0;
      QPoint a1(realb.x() + (cos(newang1/MOL_ARAD) * 10.0),
		realb.y() + (sin(newang1/MOL_ARAD) * 10.0));
      QPoint a2(realb.x() + (cos(newang2/MOL_ARAD) * 10.0),
		realb.y() + (sin(newang2/MOL_ARAD) * 10.0));
      drawLine(realb, a1, 1, c1);
      drawLine(realb, a2, 1, c1);
    }
  }
  // set output to screen
  outputDevice = OUTPUT_SCREEN;
  repaint(false);
}
