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

  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 "mvTexture.h"

#include "vtk_glew.h"

#include "mvDataObject.h"
#include "vtkDataArray.h"
#include "vtkEquirectangularToCubeMapTexture.h"
#include "vtkImageData.h"
#include "vtkInformation.h"
#include "vtkMath.h"
#include "vtkOpenGLTexture.h"
#include "vtkPointData.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkTextureObject.h"
#include "vtkXMLDataElement.h"
#include "vtkXMLDataReader.h"

mvTexture::mvTexture()
{
  this->OpenGLTexture.TakeReference(vtkOpenGLTexture::New());
  this->OpenGLTexture->SetInterpolate(1);
  this->OpenGLTexture->SetMipmap(1);
}

mvTexture::~mvTexture()
{
  this->FreeMemory();
}

void mvTexture::FreeMemory()
{
  if (this->CubeMapTexture)
  {
    this->CubeMapTexture->SetTextureObject(nullptr);
  }
  else if (this->OpenGLTexture)
  {
    this->OpenGLTexture->SetTextureObject(nullptr);
  }
  this->TextureObject = nullptr;
}

void mvTexture::LoadData(vtkXMLDataElement *datael)
{
  datael->GetScalarAttribute("ImageDataID", this->ImageDataID);
  int tmp = 1;
  datael->GetScalarAttribute("Repeat", tmp);
  this->OpenGLTexture->SetRepeat(tmp);
  tmp = 0;
  datael->GetScalarAttribute("UseSRGBColorSpace", tmp);
  this->OpenGLTexture->SetUseSRGBColorSpace(tmp);


  char const * tclass = datael->GetAttribute("ClassName");
  if (tclass && !strcmp("vtkEquirectangularToCubeMapTexture", tclass))
  {
    this->CubeMapTexture.TakeReference(vtkEquirectangularToCubeMapTexture::New());
    this->CubeMapTexture->SetInputTexture(this->OpenGLTexture);
    this->OpenGLTexture->SetMipmap(0);
  }
  else
  {
    this->OpenGLTexture->SetRepeat(1);
    this->OpenGLTexture->SetMaximumAnisotropicFiltering(4.0);
  }
}

vtkOpenGLTexture *mvTexture::GetOutputTexture()
{
  return this->CubeMapTexture != nullptr ? this->CubeMapTexture.Get() : this->OpenGLTexture;
}

void mvTexture::SetInputTextureObject(vtkTextureObject *to)
{
  this->OpenGLTexture->SetTextureObject(to);
}

void mvTexture::CreateInputTextureObject(mvDataObject &dobj, vtkOpenGLRenderWindow *rw)
{

  this->TextureObject.TakeReference(vtkTextureObject::New());
  vtkTextureObject *tobj = this->TextureObject;

  tobj->SetAutoParameters(0);
  tobj->SetContext(rw);
  tobj->SetMagnificationFilter(vtkTextureObject::Linear);
  tobj->SetMinificationFilter(vtkTextureObject::LinearMipmapLinear);
  tobj->SetUseSRGBColorSpace(
    this->GetOutputTexture()->GetUseSRGBColorSpace());
  tobj->SetMaximumAnisotropicFiltering(4.0);

  vtkXMLDataReader *rdr = dobj.GetLODReader(dobj.GetSmallestLOD());
  vtkImageData *id = vtkImageData::SafeDownCast(rdr->GetOutputAsDataSet());
  int numComp = id->GetNumberOfScalarComponents();

  if (dobj.GetNumberOfLODs() > 1)
  {
    // allocate all levels
    tobj->SetMaxLevel(dobj.GetSmallestLOD());
    for (int lvl = dobj.GetSmallestLOD(); lvl >= 0; --lvl)
    {
      rdr = dobj.GetLODReader(lvl);
      rdr->UpdateInformation();
      vtkInformation *info = rdr->GetOutputInformation(0);
      int extent[6];
      info->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(),extent);


      // allocate the data for the texture
      tobj->SetBaseLevel(lvl);
      tobj->SetMinLOD(lvl+1);
      tobj->Allocate2D(
        extent[1] - extent[0] + 1,
        extent[3] - extent[2] + 1,
        numComp, VTK_UNSIGNED_CHAR, lvl);
      tobj->Bind();
      tobj->SendParameters();

      // load data if we have it
      if (dobj.LastLODLoaded <= lvl)
      {
        tobj->Bind();
        id = vtkImageData::SafeDownCast(rdr->GetOutputAsDataSet());
        vtkDataArray *da = id->GetPointData()->GetScalars();
        glTexSubImage2D(
          tobj->GetTarget(),
          lvl, // level
          0, // xoffset
          0, // yoffset
          extent[1] - extent[0] + 1, // width
          extent[3] - extent[2] + 1,
          numComp == 4 ? GL_RGBA : GL_RGB,
          GL_UNSIGNED_BYTE,
          da->GetVoidPointer(0)
          );
        tobj->SetMinLOD(lvl);
        tobj->Bind();
        tobj->SendParameters();
      }
    }
    // leave it with the minLOD as the last loaded one
    tobj->SetMinLOD(dobj.LastLODLoaded);
    tobj->Bind();
    tobj->SendParameters();
  }
  else
  {
    int *dims = id->GetDimensions();
    vtkDataArray *da = id->GetPointData()->GetScalars();
    tobj->Create2DFromRaw(dims[0], dims[1],
      numComp,
      VTK_UNSIGNED_CHAR,
      da->GetVoidPointer(0)
      );
    int levels = floor(log2(vtkMath::Max(dims[0],dims[1]))) + 1;
    tobj->SetMaxLevel(levels);
    tobj->SendParameters();
    glGenerateMipmap(tobj->GetTarget());
  }
}
