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

Copyright (c) 1998-2012 Kitware Inc. 28 Corporate Drive,
Clifton Park, NY, 12065, USA.

All rights reserved. No part of this software may be reproduced,
distributed,
or modified, in any form or by any means, without permission in writing from
Kitware Inc.

IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF,
EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE IS PROVIDED ON AN
"AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO
PROVIDE
MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
=========================================================================*/
#include "vtkLIDARPtsWriter.h"

#include "vtkAppendPolyData.h"
#include "vtkDataArray.h"
#include "vtkExecutive.h"
#include "vtkFloatArray.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkInformation.h"
#include "vtkPoints.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkUnsignedCharArray.h"

#define LIDAR_ASCII_SEPERATOR " "

enum FileWritingStatus
  {
    WRITE_OK = 0,
    WRITE_ERROR,
    WRITE_ABORT
  };

vtkStandardNewMacro(vtkLIDARPtsWriter);

//-----------------------------------------------------------------------------
vtkLIDARPtsWriter::vtkLIDARPtsWriter()
{
  this->FileName = NULL;
  this->OutputIsBinary = 0;
  this->WriteAsSinglePiece = false;
}

//-----------------------------------------------------------------------------
vtkLIDARPtsWriter::~vtkLIDARPtsWriter()
{
  this->SetFileName(0);
}

//----------------------------------------------------------------------------
void vtkLIDARPtsWriter::WriteData()
{
  ofstream* outfile = this->OpenOutputFile();

  if (!outfile )
    {
    vtkErrorMacro("Can not open output file for writing!");
    }
  else
    {
    if(this->WriteFile(*outfile) == WRITE_ERROR)
      {
      vtkErrorMacro("Write failed");
      }
    }

  this->CloseFile(outfile);
}


//----------------------------------------------------------------------------
int vtkLIDARPtsWriter::WriteFile(ofstream& ofp)
{
  int numInputs = this->GetNumberOfInputConnections(0);
  int returnValue = WRITE_OK;

  if (numInputs > 1 && this->WriteAsSinglePiece)
    {
    // 1st append all pieces, then write
    vtkNew<vtkAppendPolyData> append;
    for (int idx = 0; idx < numInputs; ++idx)
      {
      vtkPolyData* inputPoly = vtkPolyData::SafeDownCast(this->GetInputFromPort0(idx));
      if (!inputPoly)
        {
        continue;
        }
      append->AddInputData(inputPoly);
      }
    append->Update();

    returnValue = this->WritePoints(ofp, append->GetOutput());
    }
  else
    {
    for (int idx = 0; idx < numInputs; ++idx)
      {
      vtkPolyData* inputPoly = vtkPolyData::SafeDownCast(this->GetInputFromPort0(idx));
      if (!inputPoly)
        {
        continue;
        }

      if (this->WritePoints(ofp, inputPoly) == WRITE_ABORT)
        {
        return WRITE_ABORT;
        }
      }
    }

  return returnValue;
}

//----------------------------------------------------------------------------
int vtkLIDARPtsWriter::ComputeRequiredAxisPrecision(double min, double max)
{
  double maxComponent = fabs(min) > max ? fabs(min) : max;
  double logRange = max - min != 0 ? log10(max - min) : 0;
  double logMaxComponent = log10(maxComponent);
  int minPrecision = 7;
  if (logMaxComponent - logRange > 4)
    {
    minPrecision = static_cast<int>(ceil(logMaxComponent + (4 - logRange)));
    if (minPrecision < 7)
      {
      minPrecision = 7;
      }
    }
  // a bit of a balancing act with other axes (may end up with a larger file
  // than if we just left as already calculated), but make some effort to
  // avoid needlessly writing in scienticfic format (1.234568e+008), since
  // (for this axis) less digits if write extra precision
  // (1.234568e+008 is more digits than 123456789, 9 versus 13).  Also,
  // we don't push this as far as we "could", because it doesn mean we're
  // putting out more digits on the other axes
  if (logMaxComponent > minPrecision && logMaxComponent < minPrecision + 3)
    {
    minPrecision = ceil(logMaxComponent);
    }

  return minPrecision;
}

//----------------------------------------------------------------------------
int vtkLIDARPtsWriter::WritePoints(ofstream& ofp, vtkPolyData *inputPoly)
{
  vtkDataArray *scalars = inputPoly->GetPointData() ?
  inputPoly->GetPointData()->GetScalars("Color") : NULL;

  vtkUnsignedCharArray *rgbScalars = scalars ?
  vtkUnsignedCharArray::SafeDownCast(scalars) : NULL;

  vtkFloatArray *intensityArray = inputPoly->GetPointData() ?
    vtkFloatArray::SafeDownCast(inputPoly->GetPointData()->GetArray("Intensity")) : NULL;

  vtkPoints* points = inputPoly->GetPoints();
  if (!points)
    {
    return WRITE_OK;  // no points to write for this input
    }
  vtkTypeInt32 numPts = points->GetNumberOfPoints();
  if (numPts <= 0) return WRITE_OK;

  if(this->OutputIsBinary)
    {
    ofp.write(reinterpret_cast<char *>(&numPts), 4);
    }
  else
    {
    // determine the precision we need to write... want 4+ digits on each axis
    double *bounds = inputPoly->GetBounds();
    int xPrecision = this->ComputeRequiredAxisPrecision(bounds[0], bounds[1]);
    int yPrecision = this->ComputeRequiredAxisPrecision(bounds[2], bounds[3]);
    int zPrecision = this->ComputeRequiredAxisPrecision(bounds[4], bounds[5]);
    int requiredPrecision = xPrecision > yPrecision ? xPrecision : yPrecision;
    requiredPrecision =
      zPrecision > requiredPrecision ? zPrecision : requiredPrecision;
    ofp.precision(requiredPrecision);
    ofp << numPts << endl;
    }

  double pts[3], rgb[3];
  vtkIdType numColorTuples = rgbScalars ?
  rgbScalars->GetNumberOfTuples() : 0;
  for (vtkIdType cc=0; cc < numPts; cc++)
    {
    points->GetPoint(cc, pts);
    if(this->OutputIsBinary)
      {
      ofp.write((char *)pts,sizeof(double)*3);
      }
    else
      {
      ofp << pts[0] << LIDAR_ASCII_SEPERATOR
        << pts[1] << LIDAR_ASCII_SEPERATOR
        << pts[2];
      if (intensityArray)
        {
        ofp << LIDAR_ASCII_SEPERATOR << intensityArray->GetValue(cc);
        }
      if(rgbScalars && cc < numColorTuples)
        {
        rgbScalars->GetTuple(cc, rgb);
        ofp << LIDAR_ASCII_SEPERATOR
          << rgb[0] << LIDAR_ASCII_SEPERATOR
          << rgb[1] << LIDAR_ASCII_SEPERATOR
          << rgb[2];
        }

      if ((cc%100)==0)
        {
        this->UpdateProgress( static_cast<double>(cc) / static_cast<double>(numPts) );
        if (this->GetAbortExecute())
          {
          return WRITE_ABORT;
          }
        }

      ofp << endl;
      }
    }
  return WRITE_OK;
}

//----------------------------------------------------------------------------
ofstream* vtkLIDARPtsWriter::OpenOutputFile()
{
  if (!this->FileName || !this->FileName[0])
    {
    vtkErrorMacro("Output FileName has to be specified.");
    return 0;
    }

  ofstream* fptr = 0;
  if (this->IsBinaryType(this->FileName))
    {
    fptr = new ofstream(this->FileName, ios::out | ios::binary);
    this->OutputIsBinary = 1;
    }
  else
    {
    fptr = new ofstream(this->FileName, ios::out);
    this->OutputIsBinary = 0;
    }

  if (fptr->fail())
    {
    vtkErrorMacro(<< "Unable to open file: "<< this->FileName);
    delete fptr;
    return 0;
    }
  return fptr;
}

//----------------------------------------------------------------------------
void vtkLIDARPtsWriter::CloseFile(ios* fp)
{
  delete fp;
}

//----------------------------------------------------------------------------
bool vtkLIDARPtsWriter::IsBinaryType(const char* filename)
{
  std::string fileNameStr = filename;
  if(fileNameStr.find("bin.pts") != std::string::npos ||
    fileNameStr.find(".bin") != std::string::npos)
    {
    return true;
    }
  return false;
}

//----------------------------------------------------------------------------
void vtkLIDARPtsWriter::AddInputData(int index, vtkDataObject* input)
{
  if(input)
    {
    this->AddInputDataInternal(index, input);
    }
}

//----------------------------------------------------------------------------
int vtkLIDARPtsWriter::FillInputPortInformation(int,
  vtkInformation *info)
{
  info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataObject");
  info->Set(vtkAlgorithm::INPUT_IS_REPEATABLE(), 1);
  return 1;
}

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

  os << indent << "File Name: "
     << (this->FileName ? this->FileName : "(none)") << "\n";
  os << indent << "Write As Single Piece: "
     << (this->WriteAsSinglePiece ? "On" : "Off") << "\n";
}
//----------------------------------------------------------------------------
vtkDataObject *vtkLIDARPtsWriter::GetInputFromPort0(int connection)
{
  return this->GetExecutive()->GetInputData(0, connection);
}
