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

  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 "mvActor.h"
#include "mvDataObject.h"
#include "mvTexture.h"
#include "vtkActor.h"
#include "vtkLookupTable.h"
#include "vtkOpenGLPolyDataMapper.h"
#include "vtkOpenGLTexture.h"
#include "vtkOpenGLVertexBufferObject.h"
#include "vtkProperty.h"
#include "vtkShaderProperty.h"
#include "vtkSkybox.h"
#include "vtkTextureObject.h"
#include "vtkXMLDataElement.h"
#include "vtksys/SystemTools.hxx"

mvActor::mvActor()
{
  this->TextureID = -1;
  this->NormalTextureID = -1;
  this->AlbedoTextureID = -1;
  this->MaterialTextureID = -1;
  this->EmissiveTextureID = -1;
}

mvActor::~mvActor()
{
  this->VTKActor->Delete();
}

void mvActor::ComputeLoadPriority(
  std::map<size_t,mvTexture> &textures,
  std::map<size_t, mvDataObject> &dobjs)
{
  double factor = 0.2;
  if (this->VTKActor->GetVisibility())
  {
    factor = 1.0;
  }

  if (this->PolyDataID >= 0)
  {
    double lp = dobjs[this->PolyDataID].ComputeLoadPriority();
    dobjs[this->PolyDataID].SetLoadPriority(factor*lp);
  }

  if (this->TextureID >= 0)
  {
    size_t did = textures[this->TextureID].GetImageDataID();
    double lp = dobjs[did].ComputeLoadPriority();
    dobjs[did].SetLoadPriority(factor*lp);
  }
  if (this->NormalTextureID >= 0)
  {
    size_t did = textures[this->NormalTextureID].GetImageDataID();
    double lp = dobjs[did].ComputeLoadPriority();
    dobjs[did].SetLoadPriority(factor*lp);
  }
  if (this->AlbedoTextureID >= 0)
  {
    size_t did = textures[this->AlbedoTextureID].GetImageDataID();
    double lp = dobjs[did].ComputeLoadPriority();
    dobjs[did].SetLoadPriority(factor*lp);
  }
  if (this->MaterialTextureID >= 0)
  {
    size_t did = textures[this->MaterialTextureID].GetImageDataID();
    double lp = dobjs[did].ComputeLoadPriority();
    dobjs[did].SetLoadPriority(0.5*factor*lp);
  }
  if (this->EmissiveTextureID >= 0)
  {
    size_t did = textures[this->EmissiveTextureID].GetImageDataID();
    double lp = dobjs[did].ComputeLoadPriority();
    dobjs[did].SetLoadPriority(0.4*factor*lp);
  }
}

void mvActor::SetupTextures(std::map<size_t,mvTexture> &textures)
{
  if (this->TextureID >= 0)
  {
    this->VTKActor->SetTexture(
      textures[this->TextureID].GetOutputTexture());
  }
  if (this->NormalTextureID >= 0)
  {
    this->VTKActor->GetProperty()->SetNormalTexture(
      textures[this->NormalTextureID].GetOutputTexture());
  }
  if (this->AlbedoTextureID >= 0)
  {
    this->VTKActor->GetProperty()->SetBaseColorTexture(
      textures[this->AlbedoTextureID].GetOutputTexture());
  }
  if (this->MaterialTextureID >= 0)
  {
    this->VTKActor->GetProperty()->SetORMTexture(
      textures[this->MaterialTextureID].GetOutputTexture());
  }
  if (this->EmissiveTextureID >= 0)
  {
    this->VTKActor->GetProperty()->SetEmissiveTexture(
      textures[this->EmissiveTextureID].GetOutputTexture());
  }
}

void mvActor::LoadData(vtkXMLDataElement *actorel)
{
  if (actorel->GetAttribute("PolyDataID"))
  {
    actorel->GetScalarAttribute("PolyDataID", this->PolyDataID);
  }
  else // old style
  {
    actorel->GetScalarAttribute("PolyData", this->PolyDataID);
  }

  if (actorel->GetAttribute("TextureID"))
  {
    actorel->GetScalarAttribute("TextureID", this->TextureID);
  }
  else // old style
  {
    actorel->GetScalarAttribute("TextureData", this->TextureID);
  }

  actorel->GetScalarAttribute("normalTex", this->NormalTextureID);
  actorel->GetScalarAttribute("albedoTex", this->AlbedoTextureID);
  actorel->GetScalarAttribute("materialTex", this->MaterialTextureID);
  actorel->GetScalarAttribute("emissiveTex", this->EmissiveTextureID);

  // create the actor
  char const *aclass = actorel->GetAttribute("ClassName");
  if (aclass && !strcmp(aclass,"vtkOpenGLSkybox"))
  {
    this->VTKActor = vtkSkybox::New();
  }
  else
  {
    this->VTKActor = vtkActor::New();
    vtkNew<vtkPolyDataMapper> mapper;
    static_cast<vtkOpenGLPolyDataMapper*>(
      mapper.Get())->SetVBOShiftScaleMethod(
        vtkOpenGLVertexBufferObject::ALWAYS_AUTO_SHIFT_SCALE);
    this->VTKActor->SetMapper(mapper);
  }

  vtkActor *actor = this->VTKActor;

  double dtmp3[3];
  actorel->GetVectorAttribute("Scale", 3, dtmp3);
  actor->SetScale(dtmp3);
  actorel->GetVectorAttribute("Position", 3, dtmp3);
  actor->SetPosition(dtmp3);
  actorel->GetVectorAttribute("Origin", 3, dtmp3);
  actor->SetOrigin(dtmp3);
  actorel->GetVectorAttribute("Orientation", 3, dtmp3);
  actor->SetOrientation(dtmp3);

  actorel->GetVectorAttribute("DiffuseColor", 3, dtmp3);
  actor->GetProperty()->SetDiffuseColor(dtmp3);
  actorel->GetVectorAttribute("AmbientColor", 3, dtmp3);
  actor->GetProperty()->SetAmbientColor(dtmp3);
  actorel->GetVectorAttribute("SpecularColor", 3, dtmp3);
  actor->GetProperty()->SetSpecularColor(dtmp3);

  double dtmp;
  actorel->GetScalarAttribute("Diffuse", dtmp);
  actor->GetProperty()->SetDiffuse(dtmp);
  actorel->GetScalarAttribute("Ambient", dtmp);
  actor->GetProperty()->SetAmbient(dtmp);
  actorel->GetScalarAttribute("Specular", dtmp);
  actor->GetProperty()->SetSpecular(dtmp);
  actorel->GetScalarAttribute("SpecularPower", dtmp);
  actor->GetProperty()->SetSpecularPower(dtmp);
  actorel->GetScalarAttribute("Opacity", dtmp);
  actor->GetProperty()->SetOpacity(dtmp);
  this->Opacity = dtmp;
  if (dtmp >= 1.0)
  {
    actor->ForceOpaqueOn();
  }

  // PBR
  int itmp = VTK_GOURAUD;
  actorel->GetScalarAttribute("Interpolation", itmp);
  actor->GetProperty()->SetInterpolation(itmp);
  actorel->GetScalarAttribute("Metallic", dtmp);
  actor->GetProperty()->SetMetallic(dtmp);
  actorel->GetScalarAttribute("Roughness", dtmp);
  actor->GetProperty()->SetRoughness(dtmp);
  actorel->GetScalarAttribute("NormalScale", dtmp);
  actor->GetProperty()->SetNormalScale(dtmp);
  actorel->GetScalarAttribute("OcclusionStrength", dtmp);
  actor->GetProperty()->SetOcclusionStrength(dtmp);
  actorel->GetVectorAttribute("EmissiveFactor", 3, dtmp3);
  actor->GetProperty()->SetEmissiveFactor(dtmp3);

  // optional line width
  if (actorel->GetScalarAttribute("LineWidth", dtmp))
  {
    actor->GetProperty()->SetLineWidth(dtmp);
  }

  actor->GetMapper()->SetArrayName(
    actorel->GetAttribute("ScalarArrayName"));
  actorel->GetScalarAttribute("ScalarVisibility", itmp);
  actor->GetMapper()->SetScalarVisibility(itmp);
  actorel->GetScalarAttribute("ScalarMode", itmp);
  actor->GetMapper()->SetScalarMode(itmp);
  actorel->GetVectorAttribute("ScalarRange", 2, dtmp3);
  actor->GetMapper()->SetScalarRange(dtmp3);
  actorel->GetScalarAttribute("ScalarArrayId", itmp);
  actor->GetMapper()->SetArrayId(itmp);
  actorel->GetScalarAttribute("ScalarArrayAccessMode", itmp);
  actor->GetMapper()->SetArrayAccessMode(itmp);
  actorel->GetScalarAttribute("ScalarArrayComponent", itmp);
  actor->GetMapper()->SetArrayComponent(itmp);
  vtkLookupTable::SafeDownCast(
    actor->GetMapper()->GetLookupTable())->SetHueRange(0.7, 0.0);

  // handle shader code
  vtkXMLDataElement *sels = actorel->FindNestedElementWithName("Shaders");
  if (sels)
  {
    int numShaders = sels ? sels->GetNumberOfNestedElements() : 0;
    for (size_t i = 0; i < numShaders; i++)
    {
      vtkXMLDataElement *sel = sels->GetNestedElement(static_cast<int>(i));
      std::string type = sel->GetAttribute("Type");
      std::string name = sel->GetAttribute("Name");
      std::string replacement = sel->GetAttribute("Replacement");
      sel->GetScalarAttribute("First", itmp);
      bool first = itmp != 0;
      sel->GetScalarAttribute("All", itmp);
      bool all = itmp != 0;
      if (type == "Vertex")
      {
        actor->GetShaderProperty()->AddVertexShaderReplacement(name,first,replacement,all);
      }
      else if (type == "Geometry")
      {
        actor->GetShaderProperty()->AddGeometryShaderReplacement(name,first,replacement,all);
      }
      else
      {
        actor->GetShaderProperty()->AddFragmentShaderReplacement(name,first,replacement,all);
      }
    }
    // with shader replacements assume a greyscale lut is desired
    static_cast<vtkLookupTable*>(actor->GetMapper()->GetLookupTable())->SetSaturationRange(0.0,0.0);
    static_cast<vtkLookupTable*>(actor->GetMapper()->GetLookupTable())->SetValueRange(0.0,1.0);
  }
}

void mvActor::FreeMemory()
{
  if (this->VTKActor->GetTexture())
  {
    this->VTKActor->SetTexture(nullptr);
  }
  this->VTKActor->GetMapper()->SetInputConnection(nullptr);
}
