#include "vtkBoundingRectContextDevice2D.h"

#include "vtkAbstractContextItem.h"
#include "vtkContext2D.h"
#include "vtkImageData.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkPen.h"
#include "vtkStdString.h"
#include "vtkUnicodeString.h"

//-----------------------------------------------------------------------------
vtkStandardNewMacro(vtkBoundingRectContextDevice2D);

//-----------------------------------------------------------------------------
vtkBoundingRectContextDevice2D::vtkBoundingRectContextDevice2D()
  : Initialized(false)
  , DelegateDevice(nullptr)
{
  this->Reset();
}

//-----------------------------------------------------------------------------
vtkBoundingRectContextDevice2D::~vtkBoundingRectContextDevice2D()
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->Delete();
    this->DelegateDevice = nullptr;
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::Reset()
{
  this->Initialized = false;
  this->BoundingRect = vtkRectf(0, 0, 0, 0);
}

//-----------------------------------------------------------------------------
vtkRectf vtkBoundingRectContextDevice2D::GetBoundingRect(
  vtkAbstractContextItem* item, vtkViewport* viewport)
{
  if (!item || !viewport)
  {
    return vtkRectf();
  }

  vtkNew<vtkContextDevice2D> contextDevice;
  vtkNew<vtkBoundingRectContextDevice2D> bbDevice;
  bbDevice->SetDelegateDevice(contextDevice.Get());
  bbDevice->Begin(viewport);
  vtkNew<vtkContext2D> context;
  context->Begin(bbDevice.Get());
  item->Paint(context.Get());
  context->End();
  bbDevice->End();

  return bbDevice->GetBoundingRect();
}

//-----------------------------------------------------------------------------
vtkRectf vtkBoundingRectContextDevice2D::GetBoundingRect()
{
  return this->BoundingRect;
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::DrawString(float* point, const vtkStdString& string)
{
  this->DrawString(point, vtkUnicodeString::from_utf8(string));
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::DrawString(float* point, const vtkUnicodeString& string)
{
  if (!this->DelegateDevice)
  {
    vtkWarningMacro(<< "No DelegateDevice defined");
    return;
  }

  float bounds[4];
  this->DelegateDevice->ComputeJustifiedStringBounds(string.utf8_str(), bounds);

  this->AddPoint(point[0] + bounds[0], point[1] + bounds[1]);
  this->AddPoint(point[0] + bounds[0] + bounds[2], point[1] + bounds[1] + bounds[3]);
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::DrawMathTextString(float* point, const vtkStdString& string)
{
  if (!this->DelegateDevice)
  {
    vtkWarningMacro(<< "No DelegateDevice defined");
    return;
  }

  // Not sure if this will work for math text
  float bounds[4];
  this->DelegateDevice->ComputeJustifiedStringBounds(string.c_str(), bounds);

  this->AddPoint(point[0] + bounds[0], point[1] + bounds[1]);
  this->AddPoint(point[0] + bounds[0] + bounds[2], point[1] + bounds[1] + bounds[3]);
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::DrawImage(float p[2], float scale, vtkImageData* image)
{
  this->AddPoint(p);
  int* extent = image->GetExtent();
  this->AddPoint(p[0] + scale * extent[1], p[1] + scale * extent[3]);
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::DrawImage(const vtkRectf& pos, vtkImageData* vtkNotUsed(image))
{
  this->AddPoint(pos.GetX(), pos.GetY());
  this->AddPoint(pos.GetX() + pos.GetWidth(), pos.GetY() + pos.GetHeight());
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::SetColor4(unsigned char color[4])
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->SetColor4(color);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::SetTexture(vtkImageData* image, int properties)
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->SetTexture(image, properties);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::SetPointSize(float size)
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->SetPointSize(size);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::SetLineWidth(float width)
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->SetLineWidth(width);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::SetLineType(int type)
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->SetLineType(type);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::SetMatrix(vtkMatrix3x3* m)
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->SetMatrix(m);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::GetMatrix(vtkMatrix3x3* m)
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->GetMatrix(m);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::MultiplyMatrix(vtkMatrix3x3* m)
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->MultiplyMatrix(m);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::PushMatrix()
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->PushMatrix();
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::PopMatrix()
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->PopMatrix();
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::EnableClipping(bool enable)
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->EnableClipping(enable);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::SetClipping(int* x)
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->SetClipping(x);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::ApplyPen(vtkPen* pen)
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->ApplyPen(pen);
  }
}

//-----------------------------------------------------------------------------
vtkPen* vtkBoundingRectContextDevice2D::GetPen()
{
  if (this->DelegateDevice)
  {
    return this->DelegateDevice->GetPen();
  }

  return nullptr;
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::ApplyBrush(vtkBrush* brush)
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->ApplyBrush(brush);
  }
}

//-----------------------------------------------------------------------------
vtkBrush* vtkBoundingRectContextDevice2D::GetBrush()
{
  if (this->DelegateDevice)
  {
    return this->DelegateDevice->GetBrush();
  }

  return nullptr;
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::ApplyTextProp(vtkTextProperty* prop)
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->ApplyTextProp(prop);
  }
}

//-----------------------------------------------------------------------------
vtkTextProperty* vtkBoundingRectContextDevice2D::GetTextProp()
{
  if (this->DelegateDevice)
  {
    return this->DelegateDevice->GetTextProp();
  }

  return nullptr;
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::DrawPoly(
  float* points, int n, unsigned char* colors, int nc_comps)
{
  this->DrawLines(points, n, colors, nc_comps);
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::DrawLines(
  float* f, int n, unsigned char* vtkNotUsed(colors), int vtkNotUsed(nc_comps))
{
  if (f == nullptr)
  {
    return;
  }

  for (int i = 0; i < n; ++i)
  {
    this->AddPoint(f + 2 * i);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::DrawPoints(
  float* points, int n, unsigned char* vtkNotUsed(colors), int vtkNotUsed(nc_comps))
{
  if (points == nullptr)
  {
    return;
  }

  for (int i = 0; i < n; ++i)
  {
    this->AddPoint(points + 2 * i);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::DrawPointSprites(vtkImageData* vtkNotUsed(sprite),
  float* points, int n, unsigned char* vtkNotUsed(colors), int vtkNotUsed(nc_comps))
{
  if (points == nullptr || this->DelegateDevice)
  {
    return;
  }

  // Point sprites are squares whose sides are the current pen's width
  float penWidth = this->DelegateDevice->GetPen()->GetWidth();
  float halfWidth = 0.5 * penWidth;
  for (int i = 0; i < n; ++i)
  {
    float x = points[2 * i + 0];
    float y = points[2 * i + 1];
    this->AddPoint(x - halfWidth, y - halfWidth);
    this->AddPoint(x + halfWidth, y - halfWidth);
    this->AddPoint(x - halfWidth, y + halfWidth);
    this->AddPoint(x + halfWidth, y + halfWidth);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::DrawMarkers(int vtkNotUsed(shape), bool vtkNotUsed(highlight),
  float* points, int n, unsigned char* colors, int nc_comps)
{
  if (points == nullptr)
  {
    return;
  }

  this->DrawPointSprites(nullptr, points, n, colors, nc_comps);
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::DrawEllipseWedge(float x, float y, float outRx, float outRy,
  float inRx, float inRy, float startAngle, float stopAngle)
{
  if (outRy == 0.0f && outRx == 0.0f)
  {
    // we make sure maxRadius will never be nullptr.
    return;
  }

  // Add one point per degree of rotation, which is probably good enough.
  while (startAngle <= stopAngle)
  {
    double a = vtkMath::RadiansFromDegrees(startAngle);
    this->AddPoint(outRx * cos(a) + x, outRy * sin(a) + y);
    this->AddPoint(inRx * cos(a) + x, inRy * sin(a) + y);
    startAngle += 1.0;
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::DrawEllipticArc(
  float x, float y, float rX, float rY, float startAngle, float stopAngle)
{
  // Add one point per degree of rotation, which is probably good enough.
  while (startAngle <= stopAngle)
  {
    double a = vtkMath::RadiansFromDegrees(startAngle);
    this->AddPoint(rX * cos(a) + x, rY * sin(a) + y);
    startAngle += 1.0;
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::ComputeStringBounds(
  const vtkStdString& string, float bounds[4])
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->ComputeStringBounds(string, bounds);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::ComputeStringBounds(
  const vtkUnicodeString& string, float bounds[4])
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->ComputeStringBounds(string, bounds);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::ComputeJustifiedStringBounds(
  const char* string, float bounds[4])
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->ComputeJustifiedStringBounds(string, bounds);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::Begin(vtkViewport* viewport)
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->Begin(viewport);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::End()
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->End();
  }
}

//-----------------------------------------------------------------------------
bool vtkBoundingRectContextDevice2D::GetBufferIdMode() const
{
  if (this->DelegateDevice)
  {
    return this->DelegateDevice->GetBufferIdMode();
  }
  return false;
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::BufferIdModeBegin(vtkAbstractContextBufferId* bufferId)
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->BufferIdModeBegin(bufferId);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::BufferIdModeEnd()
{
  if (this->DelegateDevice)
  {
    this->DelegateDevice->BufferIdModeEnd();
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::AddPoint(float x, float y)
{
  float point[2] = { x, y };
  this->AddPoint(point);
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::AddPoint(float pt[2])
{
  if (this->Initialized)
  {
    this->BoundingRect.AddPoint(pt);
  }
  else
  {
    this->Initialized = true;
    this->BoundingRect = vtkRectf(pt[0], pt[1], 0.0f, 0.0f);
  }
}

//-----------------------------------------------------------------------------
void vtkBoundingRectContextDevice2D::AddRect(const vtkRectf& rect)
{
  if (this->Initialized)
  {
    this->BoundingRect.AddRect(rect);
  }
  else
  {
    this->Initialized = true;
    this->BoundingRect = rect;
  }
}
