//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================
#include "vtkCMBConeSource.h"

#include "vtkCellArray.h"
#include "vtkDoubleArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPolyData.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkTransform.h"

#include <math.h>

vtkStandardNewMacro(vtkCMBConeSource);

// Construct with default resolution 6, height 1.0, radius 0.5, and capping
// on.
vtkCMBConeSource::vtkCMBConeSource(int res)
{
  res = (res < 3 ? 3 : res);
  this->Resolution = res;
  this->Height = 1.0;
  this->BaseRadius = 0.5;
  this->TopRadius = 0.0;
  this->Capping = 1;

  this->BaseCenter[0] = 0.0;
  this->BaseCenter[1] = 0.0;
  this->BaseCenter[2] = 0.0;

  this->Direction[0] = 1.0;
  this->Direction[1] = 0.0;
  this->Direction[2] = 0.0;

  this->SetNumberOfInputPorts(0);
}

int vtkCMBConeSource::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
{
  // get the info objects
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

  double angle;
  int numPolys, numPts;
  double x[3], xbot;
  int i;
  vtkIdType pts[VTK_CELL_SIZE];
  vtkPoints* newPoints;
  vtkCellArray* newLines = 0;
  vtkCellArray* newPolys = 0;
  vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));
  // for streaming
  int piece;
  int numPieces;
  int maxPieces;
  int start, end;
  int createBottom;
  int createTop;
  int numCaps = (BaseRadius > 0.0);
  numCaps += (TopRadius > 0.0);
  int res = (this->Resolution < 3) ? 3 : this->Resolution;
  piece = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER());
  if (piece >= res && !(piece == 0 && res == 0))
  {
    return 1;
  }
  numPieces = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES());
  maxPieces = res;
  if (numPieces > maxPieces)
  {
    numPieces = maxPieces;
  }
  if (piece >= maxPieces)
  {
    // Super class should do this for us,
    // but I put this condition in any way.
    return 1;
  }
  start = maxPieces * piece / numPieces;
  end = (maxPieces * (piece + 1) / numPieces) - 1;
  createBottom = (this->Capping && (start == 0) && (BaseRadius > 0.0));
  createTop = (this->Capping && (start == 0) && (TopRadius > 0.0));

  vtkDebugMacro("CmbConeSource Executing");

  angle = 2.0 * 3.141592654 / res;

  int createCaps = createBottom + createTop;

  // Are we dealing with the degenerate case of both
  // radii  == 0
  if (!numCaps)
  {
    if (!piece)
    {
    }
    return 1;
  }

  // Set things up; allocate memory
  //

  // Are we dealing with a trucated cone?
  if (numCaps == 2)
  {
    // Are we creating endcaps
    if (createCaps)
    {
      // piece 0 has  2caps.
      numPts = (res * 2);
      numPolys = 2 * (end - start + 2);
    }
    else
    {
      numPts = 2 * (end - start + 2);
      numPolys = 2 * (end - start + 1);
    }
  }
  else if (createCaps == 1)
  {
    // piece 0 has 1 cap.
    numPts = res + 1;
    numPolys = end - start + 2;
  }
  else
  {
    numPts = end - start + 3;
    numPolys = end - start + 1;
  }
  newPolys = vtkCellArray::New();
  newPolys->Allocate(newPolys->EstimateSize(numPolys, res));
  newPoints = vtkPoints::New();
  newPoints->SetDataTypeToDouble(); //used later during transformation
  newPoints->Allocate(numPts);

  // Create cone
  //
  // Are we dealing with a tuncated cone?
  if (numCaps == 2)
  {
    if (createCaps)
    {
      for (i = 0; i < res; i++)
      {
        x[0] = 0.0;
        x[1] = this->BaseRadius * cos(i * angle);
        x[2] = this->BaseRadius * sin(i * angle);
        // Reverse the order
        pts[res - i - 1] = newPoints->InsertNextPoint(x);
      }
      newPolys->InsertNextCell(res, pts);

      for (i = 0; i < res; i++)
      {
        x[0] = this->Height;
        x[1] = this->TopRadius * cos(i * angle);
        x[2] = this->TopRadius * sin(i * angle);
        // Reverse the order
        pts[i] = newPoints->InsertNextPoint(x);
      }
      newPolys->InsertNextCell(res, pts);
    }

    if (!createCaps)
    {
      // we need to create the points also
      x[0] = this->Height;
      x[1] = this->TopRadius * cos(start * angle);
      x[2] = this->TopRadius * sin(start * angle);
      pts[0] = newPoints->InsertNextPoint(x);
      x[0] = 0.0;
      x[1] = this->BaseRadius * cos(start * angle);
      x[2] = this->BaseRadius * sin(start * angle);
      pts[1] = newPoints->InsertNextPoint(x);

      for (i = start; i <= end; ++i)
      {
        x[1] = this->BaseRadius * cos((i + 1) * angle);
        x[2] = this->BaseRadius * sin((i + 1) * angle);
        pts[2] = newPoints->InsertNextPoint(x);
        newPolys->InsertNextCell(3, pts);
        pts[1] = pts[2];
        x[0] = this->Height;
        x[1] = this->TopRadius * cos((i + 1) * angle);
        x[2] = this->TopRadius * sin((i + 1) * angle);
        pts[2] = newPoints->InsertNextPoint(x);
        newPolys->InsertNextCell(3, pts);
        pts[0] = pts[2];
      }
    }
    else
    {
      // bottom and points have already been created.
      for (i = start; i <= end; i++)
      {
        pts[0] = res + i;
        pts[1] = i;
        pts[2] = i + 1;
        if (pts[2] >= res)
        {
          pts[2] = 0;
          pts[3] = res;
        }
        else
        {
          pts[3] = res + i + 1;
        }
        newPolys->InsertNextCell(3, pts);
        pts[1] = pts[2];
        pts[2] = pts[3];
        newPolys->InsertNextCell(3, pts);
      }
    } // createCaps
  }
  else if (this->BaseRadius > 0.0)
  {
    x[0] = this->Height; // zero-centered
    x[1] = 0.0;
    x[2] = 0.0;
    pts[0] = newPoints->InsertNextPoint(x);

    xbot = 0.0;

    // General case: create Resolution triangles and single cap
    // create the bottom.
    if (createBottom)
    {
      for (i = 0; i < res; i++)
      {
        x[0] = 0.0;
        x[1] = this->BaseRadius * cos(i * angle);
        x[2] = this->BaseRadius * sin(i * angle);
        // Reverse the order
        pts[res - i - 1] = newPoints->InsertNextPoint(x);
      }
      newPolys->InsertNextCell(res, pts);
    }

    pts[0] = 0;
    if (!createBottom)
    {
      // we need to create the points also
      x[0] = xbot;
      x[1] = this->BaseRadius * cos(start * angle);
      x[2] = this->BaseRadius * sin(start * angle);
      pts[1] = newPoints->InsertNextPoint(x);
      for (i = start; i <= end; ++i)
      {
        x[1] = this->BaseRadius * cos((i + 1) * angle);
        x[2] = this->BaseRadius * sin((i + 1) * angle);
        pts[2] = newPoints->InsertNextPoint(x);
        newPolys->InsertNextCell(3, pts);
        pts[1] = pts[2];
      }
    }
    else
    {
      // bottom and points have already been created.
      for (i = start; i <= end; i++)
      {
        pts[1] = i + 1;
        pts[2] = i + 2;
        if (pts[2] > res)
        {
          pts[2] = 1;
        }
        newPolys->InsertNextCell(3, pts);
      }
    } // createBottom
  }

  // A non-default origin and/or direction requires transformation
  //
  if (this->BaseCenter[0] != 0.0 || this->BaseCenter[1] != 0.0 || this->BaseCenter[2] != 0.0 ||
    this->Direction[0] != 1.0 || this->Direction[1] != 0.0 || this->Direction[2] != 0.0)
  {
    vtkTransform* t = vtkTransform::New();
    t->Translate(this->BaseCenter[0], this->BaseCenter[1], this->BaseCenter[2]);
    double vMag = vtkMath::Norm(this->Direction);
    if (this->Direction[0] < 0.0)
    {
      // flip x -> -x to avoid instability
      t->RotateWXYZ(180.0, (this->Direction[0] - vMag) / 2.0, this->Direction[1] / 2.0,
        this->Direction[2] / 2.0);
      t->RotateWXYZ(180.0, 0, 1, 0);
    }
    else
    {
      t->RotateWXYZ(180.0, (this->Direction[0] + vMag) / 2.0, this->Direction[1] / 2.0,
        this->Direction[2] / 2.0);
    }
    double* ipts = static_cast<vtkDoubleArray*>(newPoints->GetData())->GetPointer(0);
    for (i = 0; i < numPts; i++, ipts += 3)
    {
      t->TransformPoint(ipts, ipts);
    }

    t->Delete();
  }

  // Update ourselves
  //
  output->SetPoints(newPoints);
  newPoints->Delete();

  if (newPolys)
  {
    newPolys->Squeeze(); // we may have estimated size; reclaim some space
    output->SetPolys(newPolys);
    newPolys->Delete();
  }
  else
  {
    output->SetLines(newLines);
    newLines->Delete();
  }

  return 1;
}

int vtkCMBConeSource::RequestInformation(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
{
  vtkInformation* outInfo = outputVector->GetInformationObject(0);
  outInfo->Set(CAN_HANDLE_PIECE_REQUEST(), 1);
  return 1;
}

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

  os << indent << "Resolution: " << this->Resolution << "\n";
  os << indent << "Height: " << this->Height << "\n";
  os << indent << "BaseRadius: " << this->BaseRadius << "\n";
  os << indent << "TopRadius: " << this->TopRadius << "\n";
  os << indent << "Capping: " << (this->Capping ? "On\n" : "Off\n");
  os << indent << "BaseCenter: (" << this->BaseCenter[0] << ", " << this->BaseCenter[1] << ", "
     << this->BaseCenter[2] << ")\n";
  os << indent << "Direction: (" << this->Direction[0] << ", " << this->Direction[1] << ", "
     << this->Direction[2] << ")\n";
}
