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

  Copyright (c) Kitware, Inc.
  All rights reserved.

     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 <sstream>
#include <cassert>

#include "mvDataObject.h"

#include "vtkCommand.h"
#include "vtkXMLDataElement.h"
#include "vtkXMLDataReader.h"
#include "vtkXMLPolyDataReader.h"
#include "vtkXMLImageDataReader.h"
#include "vtksys/SystemTools.hxx"

namespace
{
size_t getFileSizeInKibiBytes(std::string const &fname)
{
  ifstream aFile(fname, ios::binary);
  const auto begin = aFile.tellg();
  aFile.seekg (0, ios::end);
  const auto end = aFile.tellg();
  auto fsize = (end-begin);
  fsize /= 1024;
  return static_cast<long>(fsize);
}
}

mvDataObject::~mvDataObject()
{
}

void mvDataObject::LoadData(vtkXMLDataElement *datael, std::string const &rootDir)
{
  datael->GetScalarAttribute("DataObjectID", this->DataObjectID);
  datael->GetScalarAttribute("DataObjectType", this->DataObjectType);
  std::string fname = datael->GetAttribute("FileName");
  this->SetFileName(fname, rootDir);
}

vtkXMLDataReader *mvDataObject::MakeReader(std::string &fname)
{
  vtkXMLDataReader *rdr = nullptr;
  switch (this->DataObjectType)
  {
    case VTK_POLY_DATA:
      rdr = vtkXMLPolyDataReader::New();
      break;
    case VTK_IMAGE_DATA:
      rdr = vtkXMLImageDataReader::New();
      break;
  }
  rdr->SetFileName(fname.c_str());
  return rdr;
}

void mvDataObject::SetFileName(
  std::string const &val,
  std::string const &rootDir)
{
  this->FileName = rootDir;
  this->FileName += "/";
  this->FileName += val;

  // also look for LOD files and record them
  std::string pathName =
    vtksys::SystemTools::GetFilenamePath(this->FileName);
  std::string rootName =
    vtksys::SystemTools::GetFilenameWithoutLastExtension(this->FileName);
  std::string typeExt =
    vtksys::SystemTools::GetFilenameLastExtension(this->FileName);

  bool done = false;

  // alway stick in the full resolution
  this->LODs[0] = this->FileName;
  this->LODSizes[0] = getFileSizeInKibiBytes(this->FileName);

  // try with an index, can be more than 1
  for (int lod = 1; !done; ++lod)
  {
    std::ostringstream tmps;
    tmps << pathName << "/" << rootName << ".lod" << lod << typeExt;
    if (vtksys::SystemTools::FileExists(tmps.str().c_str(),true))
    {
      this->LODs[lod] = tmps.str();
      this->LODSizes[lod] = getFileSizeInKibiBytes(tmps.str());
    }
    else
    {
      done = true;
    }
  }

  // try without a index, (old style LODs) if so there will be only 1
  if (this->LODs.size() == 1)
  {
    std::ostringstream tmps;
    tmps << pathName << "/" << rootName << ".lod" << typeExt;
    if (vtksys::SystemTools::FileExists(tmps.str().c_str(),true))
    {
      this->LODs[1] = tmps.str();
      this->LODSizes[1] = getFileSizeInKibiBytes(tmps.str());
    }
  }

  this->LastLODLoaded = static_cast<int>(this->LODs.size());
}

void mvDataObject::FreeMemory()
{
  for (auto & r : this->LODReaders)
  {
    if (r.second)
    {
      r.second->Delete();
      r.second = nullptr;
    }
  }
  this->LastLODLoaded = static_cast<int>(this->LODs.size());
}

vtkDataSet *mvDataObject::GetOutputAsDataSet()
{
  return this->GetLODReader(this->LastLODLoaded)->GetOutputAsDataSet();
}

vtkAlgorithmOutput* mvDataObject::GetOutputPort()
{
  return this->GetLODReader(this->LastLODLoaded)->GetOutputPort();
}

int mvDataObject::GetSmallestLOD()
{
  return static_cast<int>(this->LODs.size()) - 1;
}

int mvDataObject::GetNumberOfLODs()
{
  return static_cast<int>(this->LODs.size());
}

void mvDataObject::LoadNextLOD(vtkCommand *cb)
{
  assert(this->LastLODLoaded != 0);

  vtkXMLDataReader *rdr = this->GetLODReader(this->LastLODLoaded - 1);
  if (cb)
  {
    rdr->AddObserver(vtkCommand::ProgressEvent, cb);
  }
  rdr->Update();

  this->LastLODLoaded--;
}

vtkXMLDataReader *mvDataObject::GetLODReader(int lod)
{
  if (this->LODReaders.find(lod) == this->LODReaders.end() ||
      this->LODReaders[lod] == nullptr)
  {
    this->LODReaders[lod] = this->MakeReader(this->LODs[lod]);
  }
  return this->LODReaders[lod];
}

double mvDataObject::ComputeLoadPriority()
{
  // is there any unloaded data?
  if (this->LastLODLoaded == 0)
  {
    return 0.0;
  }

  return 1000.0 / this->LODSizes[this->LastLODLoaded-1];
}
