// +-------------------------------- DISCLAIMER ---------------------------------+
// |                                                                             |
// | This application program is provided to you free of charge as an example.   |
// | Despite the considerable efforts of Euresys personnel to create a usable    |
// | example, you should not assume that this program is error-free or suitable  |
// | for any purpose whatsoever.                                                 |
// |                                                                             |
// | EURESYS does not give any representation, warranty or undertaking that this |
// | program is free of any defect or error or suitable for any purpose. EURESYS |
// | shall not be liable, in contract, in torts or otherwise, for any damages,   |
// | loss, costs, expenses or other claims for compensation, including those     |
// | asserted by third parties, arising out of or in connection with the use of  |
// | this program.                                                               |
// +-----------------------------------------------------------------------------+

#pragma once
#include "EasyImage.h"

#include <QPainter>
#include <math.h>

namespace Euresys
{
  namespace Open_eVision
  {
    class QtDrawAdapter : public EExternalDrawAdapter
    {
      const float f32Pi = 3.14159265358979323846264338327950288f;
      class PenHandler
      {
        QPen oldPen_;
        QPainter* painter_;
      public:
        PenHandler(QPainter* painter, QPen pen)
        {
          painter_ = painter;
          oldPen_ = painter_->pen();
          painter_->setPen(pen);
        }
        ~PenHandler()
        {
          painter_->setPen(oldPen_);
        }
      };

      class BrushHandler
      {
        QBrush oldBrush_;
        QPainter* painter_;
      public:
        BrushHandler(QPainter* painter, QBrush brush)
        {
          painter_ = painter;
          oldBrush_ = painter_->brush();
          painter_->setBrush(brush);
        }
        ~BrushHandler()
        {
          painter_->setBrush(oldBrush_);
        }
      };

      class FontHandler
      {
        QFont oldFont_;
        QPainter* painter_;
      public:
        FontHandler(QPainter* painter, QFont font)
        {
          painter_ = painter;
          oldFont_ = painter_->font();
          painter_->setFont(font);
        }
        ~FontHandler()
        {
          painter_->setFont(oldFont_);
        }
      };
    public:
      QtDrawAdapter(QPainter* painter);
      virtual ~QtDrawAdapter() {}

      void DrawPoint(int x1, int y1, EPen* pen)
      {
        PenHandler handler(painter_, pen ? ConvertPen(*pen) : defaultPen_);

        painter_->drawPoint(x1, y1);
      }

      void Line(int x1, int y1, int x2, int y2, EPen* pen)
      {
        PenHandler handler(painter_, pen ? ConvertPen(*pen) : defaultPen_);

        painter_->drawLine(x1, y1, x2, y2);
      }

      void Arc(int orgX, int orgY, int width, int height, float startAngle, float amplitude, EPen* pen)
      {
        PenHandler handler(painter_, pen ? ConvertPen(*pen) : defaultPen_);

        startAngle = -startAngle * 180.f / f32Pi;
        amplitude = -amplitude * 180.f / f32Pi;

        // Qt requires angles specified in 1/16th of a degree (i.e. a full circle equals 16*360=5760)
        painter_->drawArc(orgX, orgY, width, height,
                          static_cast<int>(std::round(16 * startAngle)),
                          static_cast<int>(std::round(16 * amplitude)));
      }

      void Rectangle(int orgX, int orgY, int width, int height, EPen* pen)
      {
        PenHandler handler(painter_, pen ? ConvertPen(*pen) : defaultPen_);

        painter_->drawRect(orgX, orgY, width, height);
      }

      void Polygon(const float* points, int numPoints, EPen* pen)
      {
        PenHandler handler(painter_, pen ? ConvertPen(*pen) : defaultPen_);

        std::vector<QPointF> qtPoints(numPoints);
        for (int i = 0; i < numPoints; i++)
        {
          qtPoints[i].setX(points[2*i]);
          qtPoints[i].setY(points[2*i+1]);
        }
        painter_->drawPolygon(qtPoints.data(), static_cast<int>(qtPoints.size()));
      }

      void Ellipse(int orgX, int orgY, int width, int height, EPen* pen)
      {
        PenHandler handler(painter_, pen ? ConvertPen(*pen) : defaultPen_);

        painter_->drawEllipse(orgX, orgY, width, height);
      }

      void FilledRectangle(int orgX, int orgY, int width, int height, EPen* pen, EBrush* brush)
      {
        PenHandler penHandler(painter_, pen ? ConvertPen(*pen) : defaultPen_);
        BrushHandler brushHandler(painter_, brush ? ConvertBrush(*brush) : defaultBrush_);

        painter_->drawRect(orgX, orgY, width, height);
      }

      void FilledEllipse(int orgX, int orgY, int width, int height, EPen* pen, EBrush* brush)
      {
        PenHandler penHandler(painter_, pen ? ConvertPen(*pen) : defaultPen_);
        BrushHandler brushHandler(painter_, brush ? ConvertBrush(*brush) : defaultBrush_);

        painter_->drawEllipse(orgX, orgY, width, height);
      }

      void FilledPolygon(const float* points, int numPoints, EPen* pen, EBrush* brush)
      {
        PenHandler penHandler(painter_, pen ? ConvertPen(*pen) : defaultPen_);
        BrushHandler brushHandler(painter_, brush ? ConvertBrush(*brush) : defaultBrush_);

        std::vector<QPointF> qtPoints(numPoints);
        for (int i = 0; i < numPoints; i++)
        {
          qtPoints[i].setX(points[2*i]);
          qtPoints[i].setY(points[2*i+1]);
        }
        painter_->drawPolygon(qtPoints.data(), static_cast<int>(qtPoints.size()));
      }

      void Text(const char* text, int x, int y, float orientation, EBrush* textBrush)
      {
        FontHandler fontHandler(painter_, defaultFont_);

        QPen qtTextPen;
        if (textBrush)
          qtTextPen.setBrush(ConvertBrush(*textBrush));
        else
          qtTextPen = defaultPen_;

        PenHandler textColorHandler(painter_, qtTextPen);

        float width, height;
        GetTextSize(text, &width, &height);

        painter_->save();

        painter_->translate(QPointF(x, y));
        painter_->rotate(-orientation * 180.f / f32Pi);
        painter_->drawText(0, 0, width, height, 0, QString::fromStdString(text));

        painter_->restore();
      }

      void BackedText(const char* text, int x, int y, float orientation, EBrush* textBrush, EBrush* backgroundBrush)
      {
        FontHandler fontHandler(painter_, defaultFont_);

        QPen qtTextPen;
        if (textBrush)
          qtTextPen.setBrush(ConvertBrush(*textBrush));
        else
          qtTextPen = defaultPen_;

        PenHandler textColorHandler(painter_, qtTextPen);

        float width, height;
        GetTextSize(text, &width, &height);

        QBrush qtBrush = backgroundBrush ? ConvertBrush(*backgroundBrush) : defaultBrush_;

        painter_->save();

        painter_->translate(QPointF(x, y));
        painter_->rotate(-orientation * 180.f / f32Pi);

        painter_->fillRect(QRectF(0, 0, width, height), qtBrush);

        painter_->drawText(0, 0, width, height, 0, QString::fromStdString(text));

        painter_->restore();
      }

      void Image(void* imagePtr, int imageType, int imageWidth, int imageHeight, int imagePitch, float orgX, float orgY, float width, float height)
      {
        if (!imagePtr)
          return;

        painter_->drawImage(QRectF(orgX, orgY, width, height), ConvertImage(imagePtr, imageType, imageWidth, imageHeight, imagePitch));
      }

      void ImageWithC24ColorScale(void* imagePtr, int imageType, int imageWidth, int imageHeight, int imagePitch, const void* colorScale, int numScale, float orgX, float orgY, float width, float height)
      {
        if (!imagePtr)
          return;

        painter_->drawImage(QRectF(orgX, orgY, width, height), ConvertImageC24ColorScale(imagePtr, imageType, imageWidth, imageHeight, imagePitch, colorScale, numScale));
      }

      void ImageWithBW8ColorScale(void* imagePtr, int imageType, int imageWidth, int imageHeight, int imagePitch, const void* colorScale, int numScale, float orgX, float orgY, float width, float height)
      {
        if (!imagePtr)
          return;

        painter_->drawImage(QRectF(orgX, orgY, width, height), ConvertImageBW8ColorScale(imagePtr, imageType, imageWidth, imageHeight, imagePitch, colorScale, numScale));
      }

      void GetTextSize(const char* text, float* width, float* height)
      {
        FontHandler fontHandler(painter_, defaultFont_);

        QRect bbox = painter_->boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignLeft, QString::fromStdString(text));
        *width = bbox.width();
        *height = bbox.height();
      }

      void GetExtent(int* orgX, int* orgY, int* width, int* height)
      {
        QRect rect = painter_->window();

        *orgX = rect.left();
        *orgY = rect.top();
        *width = rect.width();
        *height = rect.height();
      }

      void UseCurrentPen()
      {
        QPen qtPen = painter_->pen();
        QBrush qtBrush = qtPen.brush();

        EPen pen;

        switch (qtPen.style())
        {
        default:
        case Qt::SolidLine:
          pen.Style = EPenStyle_Solid;
          break;
        case Qt::DashLine:
          pen.Style = EPenStyle_Dash;
          break;
        case Qt::DashDotLine:
          pen.Style = EPenStyle_DashDot;
          break;
        case Qt::DashDotDotLine:
          pen.Style = EPenStyle_DashDotDot;
          break;
        case Qt::DotLine:
          pen.Style = EPenStyle_Dot;
          break;
        }

        pen.Brush.Color = ERGBColor(qtBrush.color().red(), qtBrush.color().green(), qtBrush.color().blue());
        pen.Brush.Opacity = qtBrush.color().alphaF();

        EExternalDrawAdapter::SetPen(pen);
      }

      void UseCurrentBrush()
      {
        QBrush qtBrush = painter_->brush();

        EBrush brush;

        brush.Color = ERGBColor(qtBrush.color().red(), qtBrush.color().green(), qtBrush.color().blue());
        brush.Opacity = qtBrush.color().alphaF();

        EExternalDrawAdapter::SetBrush(brush);
      }

      void SetPenInternal(EPen* pen)
      {
        defaultPen_ = ConvertPen(*pen);
      }

      void SetBrushInternal(EBrush* brush)
      {
        defaultBrush_ = ConvertBrush(*brush);
      }

      void SetFontInternal(const char* fontName, int fontSize, int fontStyle)
      {
        defaultFont_.setFamily(fontName);
        defaultFont_.setPointSizeF(fontSize);

        switch (fontStyle)
        {
        default:
        case EFontStyle_Regular:
          break;
        case EFontStyle_Bold:
          defaultFont_.setBold(true);
          break;
        case EFontStyle_BoldItalic:
          defaultFont_.setBold(true);
          defaultFont_.setItalic(true);
          break;
        case EFontStyle_Italic:
          defaultFont_.setItalic(true);
          break;
        case EFontStyle_Strikeout:
          defaultFont_.setStrikeOut(true);
          break;
        case EFontStyle_Underline:
          defaultFont_.setUnderline(true);
          break;
        }
      }

      static QImage ConvertImageC24ColorScale(void* imagePtr, int imageType, int imageWidth, int imageHeight, int imagePitch, const void* colorScale, int numScale)
      {
        QVector<QRgb> colors;

        if (colorScale)
        {
          for (int i = 0; i < numScale; i++)
          {
            EC24 c = reinterpret_cast<const EC24*>(colorScale)[i];
            colors.push_back(qRgb(c.C0, c.C1, c.C2));
          }
        }

        return ConvertImage(imagePtr, imageType, imageWidth, imageHeight, imagePitch, colors);
      }

      static QImage ConvertImageBW8ColorScale(void* imagePtr, int imageType, int imageWidth, int imageHeight, int imagePitch, const void* colorScale, int numScale)
      {
        QVector<QRgb> colors;

        if (colorScale)
        {
          for (int i = 0; i < numScale; i++)
          {
            EBW8 c = reinterpret_cast<const EBW8*>(colorScale)[i];
            colors.push_back(qRgb(c.Value, c.Value, c.Value));
          }
        }

        return ConvertImage(imagePtr, imageType, imageWidth, imageHeight, imagePitch, colors);
      }

      static QImage ConvertImage(void* imagePtr, int imageType, int imageWidth, int imageHeight, int imagePitch, const QVector<QRgb>& colors = QVector<QRgb>())
      {
        QImage qtImage;

        switch (imageType)
        {
        case EImageType_BW8:
          qtImage = QImage((const uchar*)imagePtr, imageWidth,
                           imageHeight, imagePitch,
                           colors.size() > 0 ? QImage::Format_Indexed8 : QImage::Format_Grayscale8);
          break;

        case EImageType_BW16:
        {
          qtImage = QImage(imageWidth, imageHeight, colors.size() > 0 ? QImage::Format_Indexed8 : QImage::Format_Grayscale8);

          EImageBW16 src;
          src.UseExternalBuffer(imageWidth, imageHeight, imagePtr, imagePitch);

          EImageBW8 dst;
          dst.UseExternalBuffer(imageWidth, imageHeight, qtImage.scanLine(0), qtImage.bytesPerLine());

          // Convert the image to BW8
          EasyImage::Convert(&src, &dst);
          break;
        }
        case EImageType_BW32:
        {
          qtImage = QImage(imageWidth, imageHeight, colors.size() > 0 ? QImage::Format_Indexed8 : QImage::Format_Grayscale8);

          EImageBW32 src;
          src.UseExternalBuffer(imageWidth, imageHeight, imagePtr, imagePitch);

          EImageBW8 dst;
          dst.UseExternalBuffer(imageWidth, imageHeight, qtImage.scanLine(0), qtImage.bytesPerLine());

          // Convert the image to BW8
          EasyImage::Convert(&src, &dst);
          break;
        }

        case EImageType_C24:
          qtImage = QImage((const uchar*)imagePtr, imageWidth,
                           imageHeight, imagePitch, QImage::Format_RGB888);
          qtImage = qtImage.rgbSwapped();
          break;

        case EImageType_C24A:
          qtImage = QImage((const uchar*)imagePtr, imageWidth,
                           imageHeight, imagePitch, QImage::Format_RGBA8888);
          qtImage = qtImage.rgbSwapped();
          break;

        case EImageType_C48:
        {
          qtImage = QImage(imageWidth, imageHeight, QImage::Format_RGB888);

          EImageC48 src;
          src.UseExternalBuffer(imageWidth, imageHeight, imagePtr, imagePitch);

          EImageC24 dst;
          dst.UseExternalBuffer(imageWidth, imageHeight, qtImage.scanLine(0), qtImage.bytesPerLine());

          // Convert the image to C24
          EasyImage::Convert(&src, &dst);

          qtImage = qtImage.rgbSwapped();
          break;
        }
        default:
          throw EException(EError_NotImplemented, "Feature not implemented");
        }

        if (colors.size() > 0)
          qtImage.setColorTable(colors);

        return qtImage;
      }

      static QPen ConvertPen(EPen pen)
      {
        QPen qtPen;
        qtPen.setWidth(pen.Width);

        if (pen.Brush.Color.Red >= 0)
          qtPen.setColor(QColor(pen.Brush.Color.Red, pen.Brush.Color.Green, pen.Brush.Color.Blue, static_cast<int>(255.f * pen.Brush.Opacity)));

        switch (pen.Style)
        {
        default:
        case EPenStyle_Solid:
          qtPen.setStyle(Qt::SolidLine);
          break;
        case EPenStyle_Dash:
          qtPen.setStyle(Qt::DashLine);
          break;
        case EPenStyle_DashDot:
          qtPen.setStyle(Qt::DashDotLine);
          break;
        case EPenStyle_DashDotDot:
          qtPen.setStyle(Qt::DashDotDotLine);
          break;
        case EPenStyle_Dot:
          qtPen.setStyle(Qt::DotLine);
          break;
        }

        return qtPen;
      }

      static QBrush ConvertBrush(EBrush brush)
      {
        QBrush qtBrush(Qt::SolidPattern);

        if (brush.Color.Red >= 0)
          qtBrush.setColor(QColor(brush.Color.Red, brush.Color.Green, brush.Color.Blue, static_cast<int>(255.f * brush.Opacity)));

        return qtBrush;
      }

    private:
      QPainter* painter_;
      QPen defaultPen_;
      QBrush defaultBrush_;
      QFont defaultFont_;
    };



    void QtDrawPoint(void* adapter, int x, int y, void* pen)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->DrawPoint(x, y, reinterpret_cast<EPen*>(pen));
    }

    void QtLine(void* adapter, int x1, int y1, int x2, int y2, void* pen)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->Line(x1, y1, x2, y2, reinterpret_cast<EPen*>(pen));
    }

    void QtArc(void* adapter, int orgX, int orgY, int width, int height, float startAngle, float amplitude, void* pen)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->Arc(orgX, orgY, width, height, startAngle, amplitude, reinterpret_cast<EPen*>(pen));
    }

    void QtRectangle(void* adapter, int orgX, int orgY, int width, int height, void* pen)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->Rectangle(orgX, orgY, width, height, reinterpret_cast<EPen*>(pen));
    }

    void QtEllipse(void* adapter, int orgX, int orgY, int width, int height, void* pen)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->Ellipse(orgX, orgY, width, height, reinterpret_cast<EPen*>(pen));
    }

    void QtPolygon(void* adapter, const float* points, int numPoints, void* pen)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->Polygon(points, numPoints, reinterpret_cast<EPen*>(pen));
    }

    void QtFillRectangle(void* adapter, int orgX, int orgY, int width, int height, void* pen, void* brush)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->FilledRectangle(orgX, orgY, width, height, reinterpret_cast<EPen*>(pen), reinterpret_cast<EBrush*>(brush));
    }

    void QtFillEllipse(void* adapter, int orgX, int orgY, int width, int height, void* pen, void* brush)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->FilledEllipse(orgX, orgY, width, height, reinterpret_cast<EPen*>(pen), reinterpret_cast<EBrush*>(brush));
    }

    void QtFillPolygon(void* adapter, const float* points, int numPoints, void* pen, void* brush)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->FilledPolygon(points, numPoints, reinterpret_cast<EPen*>(pen), reinterpret_cast<EBrush*>(brush));
    }

    void QtText(void* adapter, const char* text, int x, int y, float orientation, void* textBrush)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->Text(text, x, y, orientation, reinterpret_cast<EBrush*>(textBrush));
    }

    void QtBackedText(void* adapter, const char* text, int x, int y, float orientation, void* textBrush, void* backgroundBrush)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->BackedText(text, x, y, orientation, reinterpret_cast<EBrush*>(textBrush), reinterpret_cast<EBrush*>(backgroundBrush));
    }

    void QtImage(void* adapter, void* imagePtr, int imageType, int imageWidth, int imageHeight, int imagePitch, float orgX, float orgY, float width, float height)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->Image(imagePtr, imageType, imageWidth, imageHeight, imagePitch, orgX, orgY, width, height);
    }

    void QtImageWithColorScale(void* adapter, void* imagePtr, int imageType, int imageWidth, int imageHeight, int imagePitch, const void* colorScale, int numScale, float orgX, float orgY, float width, float height)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->ImageWithC24ColorScale(imagePtr, imageType, imageWidth, imageHeight, imagePitch, colorScale, numScale, orgX, orgY, width, height);
    }

    void QtImageWithGrayscaleScale(void* adapter, void* imagePtr, int imageType, int imageWidth, int imageHeight, int imagePitch, const void* colorScale, int numScale, float orgX, float orgY, float width, float height)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->ImageWithBW8ColorScale(imagePtr, imageType, imageWidth, imageHeight, imagePitch, colorScale, numScale, orgX, orgY, width, height);
    }

    void QtGetTextSize(void* adapter, const char* text, float* width, float* height)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->GetTextSize(text, width, height);
    }

    void QtGetExtent(void* adapter, int* orgX, int* orgY, int* width, int* height)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->GetExtent(orgX, orgY, width, height);
    }

    void QtUseCurrentPen(void* adapter)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->UseCurrentPen();
    }

    void QtUseCurrentBrush(void* adapter)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->UseCurrentBrush();
    }

    void QtSetPen(void* adapter, void* pen)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->SetPenInternal(reinterpret_cast<EPen*>(pen));
    }

    void QtSetBrush(void* adapter, void* brush)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->SetBrushInternal(reinterpret_cast<EBrush*>(brush));
    }

    void QtSetFont(void* adapter, char* fontName, int fontSize, int fontStyle)
    {
      reinterpret_cast<QtDrawAdapter*>(adapter)->SetFontInternal(fontName, fontSize, fontStyle);
    }

    inline QtDrawAdapter::QtDrawAdapter(QPainter* painter):
      EExternalDrawAdapter(this),
      painter_(painter)
    {
      static bool initialized = false;
      if (!initialized)
      {
        SetDrawPointFunctionPtr(reinterpret_cast<void*>(&QtDrawPoint));
        SetLineFunctionPtr(reinterpret_cast<void*>(&QtLine));
        SetArcFunctionPtr(reinterpret_cast<void*>(&QtArc));
        SetRectangleFunctionPtr(reinterpret_cast<void*>(&QtRectangle));
        SetPolygonFunctionPtr(reinterpret_cast<void*>(&QtPolygon));
        SetEllipseFunctionPtr(reinterpret_cast<void*>(&QtEllipse));
        SetFillRectangleFunctionPtr(reinterpret_cast<void*>(&QtFillRectangle));
        SetFillEllipseFunctionPtr(reinterpret_cast<void*>(&QtFillEllipse));
        SetFillPolygonFunctionPtr(reinterpret_cast<void*>(&QtFillPolygon));
        SetTextFunctionPtr(reinterpret_cast<void*>(&QtText));
        SetBackedTextFunctionPtr(reinterpret_cast<void*>(&QtBackedText));
        SetImageFunctionPtr(reinterpret_cast<void*>(&QtImage));
        SetImageWithColorScaleFunctionPtr(reinterpret_cast<void*>(&QtImageWithColorScale));
        SetImageWithGrayscaleScaleFunctionPtr(reinterpret_cast<void*>(&QtImageWithGrayscaleScale));
        SetGetTextSizeFunctionPtr(reinterpret_cast<void*>(&QtGetTextSize));
        SetGetExtentFunctionPtr(reinterpret_cast<void*>(&QtGetExtent));
        SetUseCurrentPenFunctionPtr(reinterpret_cast<void*>(&QtUseCurrentPen));
        SetUseCurrentBrushFunctionPtr(reinterpret_cast<void*>(&QtUseCurrentBrush));
        SetSetPenFunctionPtr(reinterpret_cast<void*>(&QtSetPen));
        SetSetBrushFunctionPtr(reinterpret_cast<void*>(&QtSetBrush));
        SetSetFontFunctionPtr(reinterpret_cast<void*>(&QtSetFont));
      }
    }
  }
}

