// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkProperty.h"

#include "vtkActor.h"
#include "vtkInformation.h"
#include "vtkObjectFactory.h"
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"
#include "vtkSmartPointer.h"
#include "vtkTexture.h"

#include <cstdlib>
#include <sstream>

#include <cmath>

#include <vtksys/SystemTools.hxx>

VTK_ABI_NAMESPACE_BEGIN
vtkCxxSetObjectMacro(vtkProperty, Information, vtkInformation);

//------------------------------------------------------------------------------
// Return nullptr if no override is supplied.
vtkObjectFactoryNewMacro(vtkProperty);

// Construct object with object color, ambient color, diffuse color,
// specular color, and edge color white; ambient coefficient=0; diffuse
// coefficient=0; specular coefficient=0; specular power=1; Gouraud shading;
// and surface representation. Backface and frontface culling are off.
vtkProperty::vtkProperty()
{
  // Really should initialize all colors including Color[3]
  this->Color[0] = 1;
  this->Color[1] = 1;
  this->Color[2] = 1;

  this->AmbientColor[0] = 1;
  this->AmbientColor[1] = 1;
  this->AmbientColor[2] = 1;

  this->DiffuseColor[0] = 1;
  this->DiffuseColor[1] = 1;
  this->DiffuseColor[2] = 1;

  this->SpecularColor[0] = 1;
  this->SpecularColor[1] = 1;
  this->SpecularColor[2] = 1;

  this->EdgeColor[0] = 0;
  this->EdgeColor[1] = 0;
  this->EdgeColor[2] = 0;

  this->VertexColor[0] = 0.5;
  this->VertexColor[1] = 1.0;
  this->VertexColor[2] = 0.5;

  this->EmissiveFactor[0] = 1.0;
  this->EmissiveFactor[1] = 1.0;
  this->EmissiveFactor[2] = 1.0;

  this->EdgeTint[0] = 1.0;
  this->EdgeTint[1] = 1.0;
  this->EdgeTint[2] = 1.0;

  this->CoatColor[0] = 1.0;
  this->CoatColor[1] = 1.0;
  this->CoatColor[2] = 1.0;

  this->NormalScale = 1.0;
  this->OcclusionStrength = 1.0;
  this->Metallic = 0.0;
  this->BaseIOR = 1.5;
  this->Roughness = 0.5;
  this->Anisotropy = 0.0;
  this->AnisotropyRotation = 0.0;
  this->CoatIOR = 2.0;
  this->CoatRoughness = 0.0;
  this->CoatStrength = 0.0;
  this->CoatNormalScale = 1.0;
  this->Ambient = 0.0;
  this->Diffuse = 1.0;
  this->Specular = 0.0;
  this->SpecularPower = 1.0;
  this->Opacity = 1.0;
  this->Interpolation = VTK_GOURAUD;
  this->Representation = VTK_SURFACE;
  this->EdgeVisibility = 0;
  this->VertexVisibility = 0;
  this->BackfaceCulling = 0;
  this->FrontfaceCulling = 0;
  this->PointSize = 1.0;
  this->LineWidth = 1.0;
  this->LineStipplePattern = 0xFFFF;
  this->LineStippleRepeatFactor = 1;
  this->Lighting = true;
  this->RenderPointsAsSpheres = false;
  this->RenderLinesAsTubes = false;
  this->ShowTexturesOnBackface = true;

  this->Shading = 0;
  this->MaterialName = nullptr;

  this->Information = vtkInformation::New();
  this->Information->Register(this);
  this->Information->Delete();
}

//------------------------------------------------------------------------------
vtkProperty::~vtkProperty()
{
  this->RemoveAllTextures();
  this->SetMaterialName(nullptr);

  this->SetInformation(nullptr);
}

//------------------------------------------------------------------------------
// Assign one property to another.
void vtkProperty::DeepCopy(vtkProperty* p)
{
  if (p != nullptr)
  {
    this->SetColor(p->GetColor());
    this->SetAmbientColor(p->GetAmbientColor());
    this->SetDiffuseColor(p->GetDiffuseColor());
    this->SetSpecularColor(p->GetSpecularColor());
    this->SetEdgeColor(p->GetEdgeColor());
    this->SetVertexColor(p->GetVertexColor());
    this->SetAmbient(p->GetAmbient());
    this->SetDiffuse(p->GetDiffuse());
    this->SetSpecular(p->GetSpecular());
    this->SetSpecularPower(p->GetSpecularPower());
    this->SetOpacity(p->GetOpacity());
    this->SetInterpolation(p->GetInterpolation());
    this->SetRepresentation(p->GetRepresentation());
    this->SetEdgeVisibility(p->GetEdgeVisibility());
    this->SetVertexVisibility(p->GetVertexVisibility());
    this->SetBackfaceCulling(p->GetBackfaceCulling());
    this->SetFrontfaceCulling(p->GetFrontfaceCulling());
    this->SetPointSize(p->GetPointSize());
    this->SetLineWidth(p->GetLineWidth());
    this->SetLineStipplePattern(p->GetLineStipplePattern());
    this->SetLineStippleRepeatFactor(p->GetLineStippleRepeatFactor());
    this->SetLighting(p->GetLighting());
    this->SetRenderPointsAsSpheres(p->GetRenderPointsAsSpheres());
    this->SetPoint2DShape(p->GetPoint2DShape());
    this->SetRenderLinesAsTubes(p->GetRenderLinesAsTubes());
    this->SetShading(p->GetShading());
    this->SetNormalScale(p->GetNormalScale());
    this->SetOcclusionStrength(p->GetOcclusionStrength());
    this->SetMetallic(p->GetMetallic());
    this->SetRoughness(p->GetRoughness());
    this->SetAnisotropy(p->GetAnisotropy());
    this->SetAnisotropyRotation(p->GetAnisotropyRotation());
    this->SetBaseIOR(p->GetBaseIOR());
    this->SetRoughness(p->GetRoughness());
    this->SetCoatIOR(p->GetCoatIOR());
    this->SetCoatRoughness(p->GetCoatRoughness());
    this->SetCoatStrength(p->GetCoatStrength());
    this->SetCoatNormalScale(p->GetCoatNormalScale());
    this->SetCoatColor(p->GetCoatColor());
    this->SetShowTexturesOnBackface(p->GetShowTexturesOnBackface());

    this->RemoveAllTextures();
    auto iter = p->Textures.begin();
    for (; iter != p->Textures.end(); ++iter)
    {
      this->Textures[iter->first] = iter->second;
    }
    // TODO: need to pass shader variables.
  }
}

//------------------------------------------------------------------------------
void vtkProperty::SetColor(double r, double g, double b)
{
  double newColor[3] = { r, g, b };

  // SetColor is shorthand for "set all colors"
  double* color[4] = { this->Color, this->AmbientColor, this->DiffuseColor, this->SpecularColor };

  // Set colors, and check for changes
  bool modified = false;
  for (int i = 0; i < 4; i++)
  {
    for (int j = 0; j < 3; j++)
    {
      if (color[i][j] != newColor[j])
      {
        modified = true;
        color[i][j] = newColor[j];
      }
    }
  }

  // Call Modified() if anything changed
  if (modified)
  {
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkProperty::SetColor(double a[3])
{
  this->SetColor(a[0], a[1], a[2]);
}

//------------------------------------------------------------------------------
void vtkProperty::ComputeCompositeColor(double result[3], double ambient,
  const double ambient_color[3], double diffuse, const double diffuse_color[3], double specular,
  const double specular_color[3])
{
  double norm = 0.0;
  if ((ambient + diffuse + specular) > 0)
  {
    norm = 1.0 / (ambient + diffuse + specular);
  }

  for (int i = 0; i < 3; i++)
  {
    result[i] =
      (ambient * ambient_color[i] + diffuse * diffuse_color[i] + specular * specular_color[i]) *
      norm;
  }
}

//------------------------------------------------------------------------------
double vtkProperty::ComputeReflectanceFromIOR(double IORTo, double IORFrom)
{
  return std::pow(IORTo - IORFrom, 2) / std::pow(IORTo + IORFrom, 2);
}

//------------------------------------------------------------------------------
double vtkProperty::ComputeIORFromReflectance(double reflectance, double ior)
{
  return -ior * (std::sqrt(reflectance) + 1) / (std::sqrt(reflectance) - 1);
}

//------------------------------------------------------------------------------
double vtkProperty::ComputeReflectanceOfBaseLayer()
{
  // Compute F0 of base with the environment
  // Hard coded air environment (could be modified with an other IOR)
  const double environmentIOR = 1.0;
  double baseToEnvironmentF0 =
    vtkProperty::ComputeReflectanceFromIOR(this->BaseIOR, environmentIOR);
  // Recalculate base f0 in case of a coat layer
  double baseToCoatF0 = vtkProperty::ComputeReflectanceFromIOR(this->BaseIOR, this->CoatIOR);
  // Mix F0 depending on the coat strength
  return baseToEnvironmentF0 * (1.0 - this->CoatStrength) + baseToCoatF0 * this->CoatStrength;
}

//------------------------------------------------------------------------------
// Return composite color of object (ambient + diffuse + specular). Return value
// is a pointer to rgb values.
double* vtkProperty::GetColor()
{
  vtkProperty::ComputeCompositeColor(this->Color, this->Ambient, this->AmbientColor, this->Diffuse,
    this->DiffuseColor, this->Specular, this->SpecularColor);
  return this->Color;
}

//------------------------------------------------------------------------------
// Copy composite color of object (ambient + diffuse + specular) into array
// provided.
void vtkProperty::GetColor(double rgb[3])
{
  this->GetColor();
  rgb[0] = this->Color[0];
  rgb[1] = this->Color[1];
  rgb[2] = this->Color[2];
}

//------------------------------------------------------------------------------
void vtkProperty::GetColor(double& r, double& g, double& b)
{
  this->GetColor();
  r = this->Color[0];
  g = this->Color[1];
  b = this->Color[2];
}

//------------------------------------------------------------------------------
void vtkProperty::SetTexture(const char* name, vtkTexture* tex)
{
  if (tex == nullptr)
  {
    this->RemoveTexture(name);
    return;
  }

  if ((strcmp(name, "albedoTex") == 0 || strcmp(name, "emissiveTex") == 0) &&
    !tex->GetUseSRGBColorSpace())
  {
    vtkErrorMacro("The " << name << " texture is not in sRGB color space.");
    return;
  }
  const bool texNeedLinear = strcmp(name, "materialTex") == 0 || strcmp(name, "normalTex") == 0 ||
    strcmp(name, "anisotropyTex") == 0 || strcmp(name, "coatNormalTex") == 0;
  if (texNeedLinear && tex->GetUseSRGBColorSpace())
  {
    vtkErrorMacro("The " << name << " texture is not in linear color space.");
    return;
  }

  auto iter = this->Textures.find(std::string(name));
  if (iter != this->Textures.end())
  {
    // same value?
    if (iter->second == tex)
    {
      return;
    }
    vtkWarningMacro("Texture with name " << name << " exists. It will be replaced.");
    iter->second->UnRegister(this);
  }

  tex->Register(this);
  this->Textures[name] = tex;
  this->Modified();
}

//------------------------------------------------------------------------------
vtkTexture* vtkProperty::GetTexture(const char* name)
{
  auto iter = this->Textures.find(std::string(name));
  if (iter == this->Textures.end())
  {
    return nullptr;
  }

  return iter->second;
}

//------------------------------------------------------------------------------
int vtkProperty::GetNumberOfTextures()
{
  return static_cast<int>(this->Textures.size());
}

//------------------------------------------------------------------------------
void vtkProperty::RemoveTexture(const char* name)
{
  auto iter = this->Textures.find(std::string(name));
  if (iter != this->Textures.end())
  {
    iter->second->UnRegister(this);
    this->Textures.erase(iter);
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkProperty::RemoveAllTextures()
{
  while (!this->Textures.empty())
  {
    auto iter = this->Textures.begin();
    iter->second->UnRegister(this);
    this->Textures.erase(iter);
  }
  this->Modified();
}

//------------------------------------------------------------------------------
void vtkProperty::Render(vtkActor*, vtkRenderer* renderer)
{
  // subclass would have renderer the property already.
  // this class, just handles the shading.

  if (renderer->GetSelector())
  {
    // nothing to do when rendering for hardware selection.
    return;
  }
}

//------------------------------------------------------------------------------
void vtkProperty::PostRender(vtkActor*, vtkRenderer* renderer)
{
  if (renderer->GetSelector())
  {
    // nothing to do when rendering for hardware selection.
    return;
  }
}

//------------------------------------------------------------------------------
void vtkProperty::ReleaseGraphicsResources(vtkWindow*)
{
  // vtkOpenGLRenderer releases texture resources, so we don't need to release
  // them here.
}

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

  os << indent << "Ambient: " << this->Ambient << "\n";
  os << indent << "Ambient Color: (" << this->AmbientColor[0] << ", " << this->AmbientColor[1]
     << ", " << this->AmbientColor[2] << ")\n";
  os << indent << "Diffuse: " << this->Diffuse << "\n";
  os << indent << "Diffuse Color: (" << this->DiffuseColor[0] << ", " << this->DiffuseColor[1]
     << ", " << this->DiffuseColor[2] << ")\n";
  os << indent << "Edge Color: (" << this->EdgeColor[0] << ", " << this->EdgeColor[1] << ", "
     << this->EdgeColor[2] << ")\n";
  os << indent << "Edge Visibility: " << (this->EdgeVisibility ? "On\n" : "Off\n");
  os << indent << "Vertex Color: (" << this->VertexColor[0] << ", " << this->VertexColor[1] << ", "
     << this->VertexColor[2] << ")\n";
  os << indent << "Vertex Visibility: " << (this->VertexVisibility ? "On\n" : "Off\n");
  os << indent << "Interpolation: ";
  switch (this->Interpolation)
  {
    case VTK_FLAT:
      os << "VTK_FLAT\n";
      break;
    case VTK_GOURAUD:
      os << "VTK_GOURAUD\n";
      break;
    case VTK_PHONG:
      os << "VTK_PHONG\n";
      break;
    case VTK_PBR:
      os << "VTK_PBR\n";
      break;
    default:
      os << "unknown\n";
  }
  os << indent << "Opacity: " << this->Opacity << "\n";
  os << indent << "Representation: ";
  switch (this->Representation)
  {
    case VTK_POINTS:
      os << "VTK_POINTS\n";
      break;
    case VTK_WIREFRAME:
      os << "VTK_WIREFRAME\n";
      break;
    case VTK_SURFACE:
      os << "VTK_SURFACE\n";
      break;
    default:
      os << "unknown\n";
  }
  os << indent << "Specular: " << this->Specular << "\n";
  os << indent << "Specular Color: (" << this->SpecularColor[0] << ", " << this->SpecularColor[1]
     << ", " << this->SpecularColor[2] << ")\n";
  os << indent << "Specular Power: " << this->SpecularPower << "\n";
  os << indent << "Backface Culling: " << (this->BackfaceCulling ? "On\n" : "Off\n");
  os << indent << "Frontface Culling: " << (this->FrontfaceCulling ? "On\n" : "Off\n");
  os << indent << "Point size: " << this->PointSize << "\n";
  os << indent << "Line width: " << this->LineWidth << "\n";
  os << indent << "Line stipple pattern: " << this->LineStipplePattern << "\n";
  os << indent << "Line stipple repeat factor: " << this->LineStippleRepeatFactor << "\n";
  os << indent << "Lighting: ";
  if (this->Lighting)
  {
    os << "On" << endl;
  }
  else
  {
    os << "Off" << endl;
  }
  os << indent << "Point2DShape: ";
  switch (this->Point2DShape)
  {
    case Point2DShapeType::Round:
      os << "Round\n";
      break;
    case Point2DShapeType::Square:
    default:
      os << "Square\n";
      break;
  }
  os << indent << "RenderPointsAsSpheres: " << (this->RenderPointsAsSpheres ? "On" : "Off") << endl;
  os << indent << "RenderLinesAsTubes: " << (this->RenderLinesAsTubes ? "On" : "Off") << endl;
  os << indent << "ShowTexturesOnBackface: " << (this->ShowTexturesOnBackface ? "On" : "Off")
     << endl;

  os << indent << "Shading: " << (this->Shading ? "On" : "Off") << endl;

  os << indent << "MaterialName: " << (this->MaterialName ? this->MaterialName : "(none)") << endl;

  os << indent << "Color: (" << this->Color[0] << ", " << this->Color[1] << ", " << this->Color[2]
     << ")" << endl;
  os << indent << "EmissiveFactor: (" << this->EmissiveFactor[0] << ", " << this->EmissiveFactor[1]
     << ", " << this->EmissiveFactor[2] << ")" << endl;
  os << indent << "NormalScale: " << this->NormalScale << endl;
  os << indent << "OcclusionStrength: " << this->OcclusionStrength << endl;
  os << indent << "Metallic: " << this->Metallic << endl;
  os << indent << "Roughness: " << this->Roughness << endl;
}
VTK_ABI_NAMESPACE_END
