/*=========================================================================

  Program:   Visualization Toolkit
  Module:    $RCSfile: vtkSpherePuzzle.cxx,v $

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm 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 "vtkSpherePuzzle.h"

#include "vtkAppendPolyData.h"
#include "vtkGarbageCollector.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkLinearExtrusionFilter.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkSphereSource.h"
#include "vtkTransform.h"
#include "vtkTransformFilter.h"

#include <math.h>

vtkCxxRevisionMacro(vtkSpherePuzzle, "$Revision: 1.20 $");
vtkStandardNewMacro(vtkSpherePuzzle);

//----------------------------------------------------------------------------
// Construct a new puzzle.
vtkSpherePuzzle::vtkSpherePuzzle()
{
  this->Transform = vtkTransform::New();
  this->Reset();
  this->Active = 0;

  this->SetNumberOfInputPorts(0);
}

//----------------------------------------------------------------------------
// Construct a new puzzle.
vtkSpherePuzzle::~vtkSpherePuzzle()
{
  this->Transform->Delete();
  this->Transform = NULL;
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::Reset()
{
  int idx;

  this->Modified();
  for (idx = 0; idx < 32; ++idx)
    {
    this->State[idx] = idx;
    this->PieceMask[idx] = 0;
    }
  this->Transform->Identity();
  for (idx = 0; idx < 4; ++idx)
    {
    this->Colors[0 + idx*8*3] = 255;
    this->Colors[1 + idx*8*3] = 0;
    this->Colors[2 + idx*8*3] = 0;

    this->Colors[3 + idx*8*3] = 255;
    this->Colors[4 + idx*8*3] = 175;
    this->Colors[5 + idx*8*3] = 0;

    this->Colors[6 + idx*8*3] = 255;
    this->Colors[7 + idx*8*3] = 255;
    this->Colors[8 + idx*8*3] = 0;

    this->Colors[9 + idx*8*3] = 0;
    this->Colors[10 + idx*8*3] = 255;
    this->Colors[11 + idx*8*3] = 0;

    this->Colors[12 + idx*8*3] = 0;
    this->Colors[13 + idx*8*3] = 255;
    this->Colors[14 + idx*8*3] = 255;

    this->Colors[15 + idx*8*3] = 0;
    this->Colors[16 + idx*8*3] = 0;
    this->Colors[17 + idx*8*3] = 255;

    this->Colors[18 + idx*8*3] = 175;
    this->Colors[19 + idx*8*3] = 0;
    this->Colors[20 + idx*8*3] = 255;

    this->Colors[21 + idx*8*3] = 255;
    this->Colors[22 + idx*8*3] = 50;
    this->Colors[23 + idx*8*3] = 150;
    }
}

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

  // get the ouptut
  vtkPolyData *output = vtkPolyData::SafeDownCast(
    outInfo->Get(vtkDataObject::DATA_OBJECT()));

  // We are about to create/destroy alot of objects.  Defer garbage
  // collection until we are done.
  vtkGarbageCollector::DeferredCollectionPush();

  int i, j, k, num;
  int color;
  vtkAppendPolyData *append = vtkAppendPolyData::New();
  vtkSphereSource *sphere = vtkSphereSource::New();
  vtkTransformFilter *tf = vtkTransformFilter::New();
  vtkUnsignedCharArray *scalars = vtkUnsignedCharArray::New();
  vtkPolyData *tmp;
  int count = 0;
  unsigned char r, g, b;

  scalars->SetNumberOfComponents(3);

  sphere->SetPhiResolution(4);
  sphere->SetThetaResolution(4);

  tf->SetTransform(this->Transform);
  tf->SetInput(sphere->GetOutput());

  for (j = 0; j < 4; ++j)
    {
    for (i = 0; i < 8; ++i)
      {
      color = this->State[count] * 3;
      sphere->SetStartTheta((360.0 * (double)(i) / 8.0));
      sphere->SetEndTheta((360.0 * (double)(i+1) / 8.0));
      sphere->SetStartPhi((180.0 * (double)(j) / 4.0));
      sphere->SetEndPhi((180.0 * (double)(j+1) / 4.0));
      tmp = vtkPolyData::New();
      if (this->PieceMask[count])
        { // Spheres original output is transforms input. Put it back.
        tf->Update();
        tmp->ShallowCopy(tf->GetOutput());
        }
      else
        { // Piece not involved in partial move. Just use the sphere.
        sphere->Update();
        tmp->ShallowCopy(sphere->GetOutput());
        }
      // Now create the colors for the faces.
      num = tmp->GetNumberOfPoints();
      for (k = 0; k < num; ++k)
        {
        r = this->Colors[color];
        g = this->Colors[color+1];
        b = this->Colors[color+2];
        // Lighten the active pieces
        if (this->Active && this->PieceMask[count])
          {
          r = r + (unsigned char)((255 - r) * 0.4);
          g = g + (unsigned char)((255 - g) * 0.4);
          b = b + (unsigned char)((255 - b) * 0.4);
          }
        scalars->InsertNextValue(r);
        scalars->InsertNextValue(g);
        scalars->InsertNextValue(b);
        }

      // append all the pieces.
      append->AddInput(tmp);
      tmp->FastDelete();
      ++count;
      }
    }

  append->Update();

  // Move the data to the output.
  tmp = output;
  tmp->CopyStructure(append->GetOutput());
  tmp->GetPointData()->PassData(append->GetOutput()->GetPointData());
  tmp->GetPointData()->SetScalars(scalars);

  sphere->Delete();
  scalars->Delete();
  append->Delete();
  tf->Delete();

  // We are done creating/destroying objects.
  vtkGarbageCollector::DeferredCollectionPop();

  return 1;
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::MarkHorizontal(int section)
{
  int i;

  for (i = 0; i < 32; ++i)
    {
    this->PieceMask[i] = 0;
    }
  // Find the start of the section.
  section = section * 8;
  for (i = 0; i < 8; ++i)
    {
    this->PieceMask[i+section] = 1;
    }
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::MarkVertical(int section)
{
  int i, j, offset;

  for (i = 0; i < 32; ++i)
    {
    this->PieceMask[i] = 1;
    }
  for (i = 0; i < 4; ++i)
    {
    offset = (section + i) % 8;
    for (j = 0; j < 4; ++j)
      {
      this->PieceMask[offset+(j*8)] = 0; 
      }
    }
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::MoveHorizontal(int slab, int percentage, int rightFlag)
{
  int offset;
  int tmp;
  int i;

  this->Modified();

  // Clear out previous partial moves.
  this->Transform->Identity();
  this->MarkHorizontal(slab);

  // Move zero does nothing.
  if (percentage <= 0)
    {
    return;
    }
  
  // Offset is used to determine which pieces are involved.
  offset = slab * 8;

  // Move 100 percent changes state.
  if (percentage >= 100)
    { // Just do the state change.
    if (rightFlag)
      {
      tmp = this->State[offset+7];
      for (i = 7; i > 0; --i)
        {
        this->State[i+offset] = this->State[i-1+offset]; 
        }
      this->State[offset] = tmp;
      }
    else
      {
      tmp = this->State[offset];
      for (i = 0; i < 7; ++i)
        {
        this->State[i+offset] = this->State[i+1+offset]; 
        }
      this->State[offset+7] = tmp;
      }
    return;
    }

  // Partial move.
  // This does not change the state.  It is ust for animating
  // the move.
  // Setup the pieces that are involved in the move.
  if ( ! rightFlag)
    {
    percentage = -percentage;
    }
  this->Transform->RotateZ(((double)(percentage) / 100.0)
                           * (360.0 / 8.0) );
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::MoveVertical(int half, int percentage, int rightFlag) 
{
  int tmp;
  int off0, off1, off2, off3;
  double theta;

  this->Modified();

  // Clear out previous partial moves.
  this->Transform->Identity();
  this->MarkVertical(half);

  // Move zero does nothing.
  if (percentage <= 0)
    {
    return;
    }

  off0 = (4+half) % 8;
  off1 = (5+half) % 8;
  off2 = (6+half) % 8;
  off3 = (7+half) % 8;

  // Move 100 percent changes state.
  if (percentage >= 100)
    { // Just do the state change.
    tmp = this->State[off0];
    this->State[off0] = this->State[24+off3];
    this->State[24+off3] = tmp;
  
    tmp = this->State[off1];
    this->State[off1] = this->State[24+off2];
    this->State[24+off2] = tmp;

    tmp = this->State[off2];
    this->State[off2] = this->State[24+off1];
    this->State[24+off1] = tmp;

    tmp = this->State[off3];
    this->State[off3] = this->State[24+off0];
    this->State[24+off0] = tmp;


    tmp = this->State[8+off0];
    this->State[8+off0] = this->State[16+off3];
    this->State[16+off3] = tmp;
  
    tmp = this->State[8+off1];
    this->State[8+off1] = this->State[16+off2];
    this->State[16+off2] = tmp;

    tmp = this->State[8+off2];
    this->State[8+off2] = this->State[16+off1];
    this->State[16+off1] = tmp;

    tmp = this->State[8+off3];
    this->State[8+off3] = this->State[16+off0];
    this->State[16+off0] = tmp;
    return;
    }

  // Partial move.
  // This does not change the state.  It is use for animating the move.
  if (rightFlag)
    {
    percentage = -percentage;
    }
  theta = (double)(half) * vtkMath::Pi() / 4.0;
  this->Transform->RotateWXYZ(((double)(percentage)/100.0)*(360.0/2.0),
                              sin(theta), -cos(theta), 0.0);

}

//----------------------------------------------------------------------------
int vtkSpherePuzzle::SetPoint(double x, double y, double z) 
{
  double pt[3];
  double theta, phi;
  int xi, yi;
  double xp, yp;
  double xn, yn;
  
  this->Modified();

  if (x < 0.2 && x > -0.2 && y < 0.2 && y > -0.2 && z < 0.2 && z > -0.2)
    {
    this->Active = 0;
    return 0;
    }

  // normalize
  pt[0] = x;
  pt[1] = y;
  pt[2] = z;
  vtkMath::Normalize(pt);

  // Convert this into phi and theta.
  theta = 180.0 - atan2(pt[0], pt[1]) * 180 / vtkMath::Pi();
  phi = 90.0 - asin(pt[2]) * 180 / vtkMath::Pi();

  // Compute the piece the point is in.
  xi = (int)(theta * 8.0 / 360.0);
  yi = (int)(phi * 8 / 360.0);
  xn = (theta/(360.0/8.0)) - (double)(xi);
  yn = (phi/(360.0/8.0)) - (double)(yi);

  vtkDebugMacro("point: " << x << ", " << y << ", " << z);
  vtkDebugMacro("theta: " << theta << ",  phi: " << phi);
  vtkDebugMacro("theta: " << xi << ", " << xn << ",  phi: " << yi << ", " << y);

  xp = 1.0 - xn;
  yp = 1.0 - yn;
  if (xn > 0.2 && xp > 0.2 && yn > 0.2 && yp > 0.2)
    { // Do nothing in the center of the face.
    this->Active = 0;
    return 0;
    }
  this->Active = 1;
  if (xn < xp && xn < yp && xn < yn)
    {
    this->VerticalFlag = 1;
    this->RightFlag = (yn < yp);
    this->Section = xi+2;
    this->MarkVertical(this->Section);
    return this->Section + this->VerticalFlag * 10 + this->RightFlag * 100;
    }
  if (xp < xn && xp < yp && xp < yn)
    {
    this->VerticalFlag = 1;
    this->RightFlag = (yp < yn);
    this->Section = xi+7;
    this->MarkVertical(this->Section);
    return this->Section + this->VerticalFlag * 10 + this->RightFlag * 100;
    } 
  // The remaining options move a horizontal slab.
  this->VerticalFlag = 0;
  this->RightFlag = (xn > xp);
  this->Section = yi;
  this->MarkHorizontal(this->Section);
  return this->Section + this->VerticalFlag * 10 + this->RightFlag * 100;
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::MovePoint(int percentage) 
{
  if ( ! this->Active)
    {
    return;
    }
  this->Modified();

  if (this->VerticalFlag)
    {
    this->MoveVertical(this->Section, percentage, this->RightFlag);
    }
  else
    {
    this->MoveHorizontal(this->Section, percentage, this->RightFlag);
    }
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::PrintSelf(ostream& os, vtkIndent indent)
{
  int idx;
  this->Superclass::PrintSelf(os,indent);

  os << indent << "State: " << this->State[0];
  for (idx = 1; idx < 16; ++idx)
    {
    os << ", " << this->State[idx];
    }
  os << endl;
}
