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

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/VolViewCopyright.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 "vtkKWColorImageConversionFilter.h"

#include "vtkCommand.h"
#include "vtkImageData.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkInformationVector.h"
#include "vtkInformation.h"
#include "vtkDataArray.h"

#define VV_MAX_FLOAT ((double)1.0e10)
#define VV_MIN_FLOAT ((double)1.0e-6)

vtkCxxRevisionMacro(vtkKWColorImageConversionFilter, "$Revision: 1.30 $");
vtkStandardNewMacro(vtkKWColorImageConversionFilter);

//----------------------------------------------------------------------------
void vtkKWColorImageConversionFilter::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
   
  os << indent << "IndependentComponents: " << this->IndependentComponents << "\n";
  os << indent << "AlphaFloor: " << this->AlphaFloor << "\n";
}

//----------------------------------------------------------------------------
vtkKWColorImageConversionFilter::vtkKWColorImageConversionFilter()
{
  this->IndependentComponents = 0;
  this->AlphaFloor = 0.0;
}

//----------------------------------------------------------------------------
int vtkKWColorImageConversionFilter::RequestInformation(
  vtkInformation *vtkNotUsed(request),
  vtkInformationVector **inputVector,
  vtkInformationVector *outputVector)
{
  vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation *outInfo = outputVector->GetInformationObject(0);
  
  vtkInformation *inScalarInfo = vtkDataObject::GetActiveFieldInformation(inInfo,
    vtkDataObject::FIELD_ASSOCIATION_POINTS, vtkDataSetAttributes::SCALARS);
  if (!inScalarInfo)
    {
    vtkErrorMacro("Missing scalar field on input information!");
    return 0;
    }

  int i;
  this->Conversions = 0;

  // handle the rgb issue
  if (inScalarInfo->Get(vtkDataObject::FIELD_NUMBER_OF_COMPONENTS()) == 3 &&
      !this->IndependentComponents)
    {
    vtkDataObject::SetPointDataActiveScalarInfo(outInfo, VTK_UNSIGNED_CHAR, 4);
    this->Conversions |= vtkKWColorImageConversionFilter::ConvertedToColor;
    }
  
  // handle the data type issue
//  if (inScalarInfo->Get(vtkDataObject::FIELD_ARRAY_TYPE()) == VTK_DOUBLE && 
//      !(this->Conversions & 
//        vtkKWColorImageConversionFilter::ConvertedToColor))
//    {
//    vtkDataObject::SetPointDataActiveScalarInfo(outInfo, VTK_FLOAT, -1);
//    this->Conversions |= vtkKWColorImageConversionFilter::ConvertedToFloat;
//    }

  // handle the spacing and origin float issue
  double spacing[3];
  inInfo->Get(vtkDataObject::SPACING(), spacing);
  
  double origin[3];
  inInfo->Get(vtkDataObject::ORIGIN(), origin);
  
  // if the origin is too big deal with it
  if (origin[0] < -VV_MAX_FLOAT ||
      origin[0] > VV_MAX_FLOAT ||
      origin[1] < -VV_MAX_FLOAT ||
      origin[1] > VV_MAX_FLOAT ||
      origin[2] < -VV_MAX_FLOAT ||
      origin[2] > VV_MAX_FLOAT)
    {
    double origin_zero[3] = {0, 0, 0};
    outInfo->Set(vtkDataObject::ORIGIN(), origin_zero, 3);
    this->Conversions |= vtkKWColorImageConversionFilter::ShiftedOrigin;
    }

  // if the spacing is too big or small deal with it but we need to maintain
  // the pixel aspect ratio if possible. So find the max and min spacing
  double minSpacing, maxSpacing;
  minSpacing = spacing[0];
  maxSpacing = spacing[1];
  for (i = 0; i < 3; ++i)
    {
    if (spacing[i] < minSpacing)
      {
      minSpacing = spacing[i];
      }
    if (spacing[i] > maxSpacing)
      {
      maxSpacing = spacing[i];
      }
    }

  if (minSpacing == 0.0) // to avoid divide by zero
    {
    vtkErrorMacro("One of the image spacing is zero!");
    return 0;
    }

  double dspacing[3];
  dspacing[0] = spacing[0];
  dspacing[1] = spacing[1];
  dspacing[2] = spacing[2];

  // if the origin is much larger than the spacing we set the origin to zero
  if (fabs(origin[0]/minSpacing)*VV_MIN_FLOAT > 1.0 ||
      fabs(origin[1]/minSpacing)*VV_MIN_FLOAT > 1.0 ||
      fabs(origin[2]/minSpacing)*VV_MIN_FLOAT > 1.0)
    {
    double origin_zero[3] = {0, 0, 0};
    outInfo->Set(vtkDataObject::ORIGIN(), origin_zero, 3);
    this->Conversions |= vtkKWColorImageConversionFilter::ShiftedOrigin;
    }

  // if the apsect ratio is too large then we must compress it while we might
  // be able to do something with an aspect ratio of 1e10 to be honest that
  // seems insane so we will use a lower threshold here. An aspect ratio of
  // 1e5 should be tons.
  if (maxSpacing/minSpacing > 1.0e5)
    {
    for (i = 0; i < 3; ++i)
      {
      if (dspacing[i]/minSpacing > 1.0e5)
        {
        dspacing[i] = 1.0e5;
        }
      }
    maxSpacing = minSpacing*1.0e5;
    this->Conversions |= 
      vtkKWColorImageConversionFilter::CompressedAspectRatio;
    }

  // OK now we know the aspect ratio is reasonable we can look at the scale
  // of the spacing.
  if (maxSpacing > VV_MAX_FLOAT)
    {
    for (i = 0; i < 3; ++i)
      {
      dspacing[i] = dspacing[i]*VV_MAX_FLOAT/maxSpacing;
      }    
    minSpacing = minSpacing*VV_MAX_FLOAT/maxSpacing;
    maxSpacing = VV_MAX_FLOAT;
    this->Conversions |= vtkKWColorImageConversionFilter::CompressedSpacing;
    }
  if (fabs(minSpacing) < VV_MIN_FLOAT)
    {
    for (i = 0; i < 3; ++i)
      {
      dspacing[i] = dspacing[i]*VV_MIN_FLOAT/minSpacing;
      }    
    maxSpacing = maxSpacing*VV_MIN_FLOAT/minSpacing;
    minSpacing = VV_MIN_FLOAT;
    this->Conversions |= vtkKWColorImageConversionFilter::CompressedSpacing;
    }
  
  // now set the fricking spacing
  outInfo->Set(vtkDataObject::SPACING(), dspacing, 3);

  return 1;
}

template <class T>
void vtkKWColorImageConversionFunction(
  vtkKWColorImageConversionFilter *self,
  vtkImageData *idata, vtkImageData *odata, T *)
{
  // get the data
  T *data = (T *)idata->GetScalarPointer(idata->GetExtent()[0],
                                         idata->GetExtent()[2],
                                         idata->GetUpdateExtent()[4]);
  unsigned char *outPtr = 
    (unsigned char *)odata->GetScalarPointer(idata->GetExtent()[0],
                                             idata->GetExtent()[2],
                                             idata->GetUpdateExtent()[4]);
  int *dim = idata->GetDimensions();
  
  // now loop through the slice
  int x,y, i;
  float h,s,v;
  double floorVal = self->GetAlphaFloor( );
  for (y = 0; y < dim[1]; ++y)
    {
    for (x = 0; x < dim[0]; ++x)
      {
      vtkMath::RGBToHSV(data[0]/255.0f,data[1]/255.0f,data[2]/255.0f,
                        &h,&s,&v);
      for (i = 0; i < 3; ++i)
        {
        *outPtr = (unsigned char)*data;
        data++;
        outPtr++;
        }
      *outPtr = (unsigned char) (floorVal > h) ? (floorVal*255.0f) : (h*255.0f);
      outPtr++;
      }
    }
}

template <class T>
void vtkKWColorImageConversionAndScaleFunction(
  vtkKWColorImageConversionFilter *self,
  vtkImageData *idata, vtkImageData *odata, double scale, 
  double shift, T *)
{
  // get the data
  T *data = (T *)idata->GetScalarPointer(idata->GetExtent()[0],
                                         idata->GetExtent()[2],
                                         idata->GetUpdateExtent()[4]);
  unsigned char *outPtr = 
    (unsigned char *)odata->GetScalarPointer(idata->GetExtent()[0],
                                             idata->GetExtent()[2],
                                             idata->GetUpdateExtent()[4]);
  int *dim = idata->GetDimensions();
  
  // now loop through the slice
  int x,y, i;
  float h,s,v;
  float fdata[3];
  double floorVal = self->GetAlphaFloor( );
  for (y = 0; y < dim[1]; ++y)
    {
    for (x = 0; x < dim[0]; ++x)
      {
      fdata[0] = (data[0] + shift)*scale;
      fdata[1] = (data[1] + shift)*scale;
      fdata[2] = (data[2] + shift)*scale;      
      vtkMath::RGBToHSV(fdata[0]/255.0f,fdata[1]/255.0f,fdata[2]/255.0f,
                        &h, &s, &v);
      for (i = 0; i < 3; ++i)
        {
        *outPtr = (unsigned char)fdata[i];
        data++;
        outPtr++;
        }
      *outPtr = (unsigned char) (floorVal > h) ? (floorVal*255.0f) : (h*255.0f);
      outPtr++;
      }
    }
}

void vtkKWDoubleImageConversionFunction(
  vtkKWColorImageConversionFilter *vtkNotUsed(self),
  vtkImageData *idata, vtkImageData *odata, double scale)
{
  // get the data
  double *data = 
    (double *)idata->GetScalarPointer(idata->GetExtent()[0],
                                      idata->GetExtent()[2],
                                      idata->GetUpdateExtent()[4]);
  float *outPtr = 
    (float *)odata->GetScalarPointer(idata->GetExtent()[0],
                                     idata->GetExtent()[2],
                                     idata->GetUpdateExtent()[4]);
  int *dim = idata->GetDimensions();
  
  // now loop through the slice
  int x,y, i;
  int numCom = idata->GetNumberOfScalarComponents();
  for (y = 0; y < dim[1]; ++y)
    {
    for (x = 0; x < dim[0]; ++x)
      {
      for (i = 0; i < numCom; ++i)
        {
        *outPtr = (float)(*data*scale);
        data++;
        outPtr++;
        }
      }
    }
}


void vtkKWColorImageConversionFilter::ComputeScaling(double totalRange[2], 
                                                     double &scaling, 
                                                     double &shift)
{
  scaling = 1.0;
  shift = 0.0;
  
  // double to float
  if (this->Conversions & vtkKWColorImageConversionFilter::ConvertedToFloat)
    {
    // too big negative
    if (totalRange[0] < -VTK_LARGE_FLOAT)
      {
      scaling = -VTK_LARGE_FLOAT/totalRange[0];
      totalRange[0] = -VTK_LARGE_FLOAT;
      totalRange[1] = totalRange[1]*scaling;
      this->Conversions |= 
        vtkKWColorImageConversionFilter::CompressedScalarRange;
      }
    // too big positive
    if (totalRange[1] > VTK_LARGE_FLOAT)
      {
      scaling = scaling*VTK_LARGE_FLOAT/totalRange[1];
      totalRange[0] = totalRange[0]*VTK_LARGE_FLOAT/totalRange[1];
      totalRange[1] = VTK_LARGE_FLOAT;
      this->Conversions |= 
        vtkKWColorImageConversionFilter::CompressedScalarRange;    
      }
    // too small
    if (fabs(totalRange[0]) < 1.0e-30 &&
        fabs(totalRange[1]) < 1.0e-30)
      {
      this->Conversions |= 
        vtkKWColorImageConversionFilter::CompressedScalarRange;    
      // scale so that one the large of range terms is around e0
      if (fabs(totalRange[0]) > fabs(totalRange[1]))
        {
        // scaling cannot have been set above if we are in here
        scaling = 1.0/totalRange[0];
        totalRange[0] = 1.0;
        totalRange[1] = totalRange[1]*scaling;
        }
      else
        {
        // scaling cannot have been set above if we are in here
        scaling = 1.0/totalRange[1];
        totalRange[0] = totalRange[0]*scaling;
        totalRange[1] = 1.0;
        }
      }
    }
  else // then it must be a color conversion and we want 0 to 255
    {
    // first do a shift to zero
    if (totalRange[0] < 0)
      {
      shift = totalRange[0];
      totalRange[0] = 0;
      totalRange[1] = totalRange[1] + shift;
      this->Conversions |= 
        vtkKWColorImageConversionFilter::CompressedScalarRange;    
      }
    // too big positive
    if (totalRange[1] > 255.0)
      {
      scaling = 255.0/totalRange[1];
      totalRange[0] = totalRange[0]*255.0/totalRange[1];
      totalRange[1] = 255.0;
      this->Conversions |= 
        vtkKWColorImageConversionFilter::CompressedScalarRange;    
      }
    // too small
    if (totalRange[1] < 255.0)
      {
      // scale so that totalRange[1] is 255
      scaling = 255.0/totalRange[1];
      totalRange[0] = totalRange[0]*scaling;
      totalRange[1] = 255.0;
      this->Conversions |= 
        vtkKWColorImageConversionFilter::CompressedScalarRange;    
      }
    }    
}

//----------------------------------------------------------------------------
// this function became too long... If you are going to add more to it
// then it should be factorized
int vtkKWColorImageConversionFilter::RequestData(
  vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector,
  vtkInformationVector* outputVector)
{
  // get the input info and data objects
  vtkInformation* in1Info = inputVector[0]->GetInformationObject(0);
  vtkImageData *input = vtkImageData::SafeDownCast(
    in1Info->Get(vtkDataObject::DATA_OBJECT()));

  vtkInformation *outInfo = outputVector->GetInformationObject(0);
  vtkImageData *output = vtkImageData::SafeDownCast(
    outInfo->Get(vtkDataObject::DATA_OBJECT()));
  
  int idx;
  int piece;
  
  // This filters input extent and output extents
  // should be the same.
  output->SetExtent(input->GetUpdateExtent());

  // we can pass data as long as we do not need to worry
  // about a couple of conversions such as to color or to float

  if (!(this->Conversions & 
        vtkKWColorImageConversionFilter::ConvertedToColor) &&
      !(this->Conversions & 
        vtkKWColorImageConversionFilter::ConvertedToFloat))   
    {
    output->GetPointData()->PassData(input->GetPointData());
    return 1;
    }

  // get the extent
  int *uExt = output->GetUpdateExtent();

  // if the original data type is double then we may have a scalar range
  // problem. So we must first read in all the data in a first pass to
  // determine the actual scalar range. Then we can properly process the
  // data. We do not check for data values that are too small if the overall
  // range isn't too small. e.g. if the data range happens to be 0 to 1 but
  // there are some 1.0e-200 values in there as well we do not fix that. But
  // if the total range is 1.0e-200 to 2.0e-200 then we will fix it.
  double totalRange[2];
  totalRange[0] = VTK_DOUBLE_MAX;
  totalRange[1] = VTK_DOUBLE_MIN;
  if (input->GetScalarType() != VTK_UNSIGNED_CHAR)
    {
    double range[2];
    // now start the loop over the number of pieces
    for (piece = uExt[4]; piece <= uExt[5]; piece++)
      {
      input->SetUpdateExtent(uExt[0], uExt[1], uExt[2], uExt[3], 
                             piece, piece);
      input->PropagateUpdateExtent();
      input->UpdateData();

      if (input->GetPointData() && 
          input->GetPointData()->GetScalars())
        {
        for (idx = 0; idx < input->GetNumberOfScalarComponents(); ++idx)
          {
          input->GetPointData()->GetScalars()->GetRange(range, idx);
          // check min max
          if (range[0] < totalRange[0])
            {
            totalRange[0] = range[0];
            }
          if (range[1] > totalRange[1])
            {
            totalRange[1] = range[1];
            }
          }
        }
      this->UpdateProgress(0.5*(piece - uExt[4] + 1.0)/
                           (uExt[5] - uExt[4] + 1.0));
      }
    }
  else
    {
    totalRange[0] = 0;
    totalRange[1] = 255;
    }

  // OK now that we know the range, do we need to scale?
  // for the double to float conversion there is one scaling,
  // for conversion to color there is a different scaling
  double scaling, shift;
  this->ComputeScaling(totalRange, scaling, shift);
  
  // if we made it here then the data must be processed
  output->AllocateScalars();

  // now start the loop over the number of pieces
  int input_had_no_scalars = 0;
  for (piece = uExt[4]; piece <= uExt[5]; piece++)
    {
    input->SetUpdateExtent(uExt[0], uExt[1], uExt[2], uExt[3], piece, piece);
    input->PropagateUpdateExtent();
    input->UpdateData();
    if (input->GetPointData() && 
        input->GetPointData()->GetScalars())
      {
      // copy the resulting data into the output buffer
      if (output->GetScalarType() == VTK_FLOAT)
        {
        vtkKWDoubleImageConversionFunction(this, input, output, scaling);
        }
      else if (scaling != 1.0 || shift != 0.0)
        {
        switch (output->GetScalarType())
          {
        vtkTemplateMacro(vtkKWColorImageConversionAndScaleFunction( 
                           this, input, output, scaling, shift, 
                           (VTK_TT *)0));
          default:
            vtkErrorMacro(<< "Execute: Unknown ScalarType");
            return 1;
          }
      }
      else 
        {
        switch (output->GetScalarType())
          {
          vtkTemplateMacro(vtkKWColorImageConversionFunction( 
                             this, input, output, (VTK_TT *)0));
          default:
            vtkErrorMacro(<< "Execute: Unknown ScalarType");
            return 1;
          }
        }
      }
    else
      {
      input_had_no_scalars++;
      }
    
    // update the progress
    if (input->GetScalarType() != VTK_UNSIGNED_CHAR)
      {
      this->UpdateProgress(0.5+0.5*(piece - uExt[4] + 1.0)/
                           (uExt[5] - uExt[4] + 1.0));
      }
    else
      {
      this->UpdateProgress((piece - uExt[4] + 1.0)/
                           (uExt[5] - uExt[4] + 1.0));
      }
    }

  // If at any point the input didn't give us point scalars
  // deallocate ours. It may happen if we try to load a .vti file that
  // describes structured data but does not have scalars on the point
  // data... (though I would rather we detect that earlier)

  if (input_had_no_scalars)
    {
    output->GetPointData()->SetScalars(NULL);
    }

  return 1;
}
