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

  Program:   Visualization Toolkit
  Module:    vtkLightKit.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

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

#include "vtkLight.h"
#include "vtkObjectFactory.h"
#include "vtkPiecewiseFunction.h"
#include "vtkRenderer.h"

vtkStandardNewMacro(vtkLightKit);

static const char* vtkLightKitTypeStrings[] = { "KeyLight", "FillLight", "BackLight", "HeadLight",
  nullptr };

static const char* vtkLightKitSubTypeStrings[] = { "Warmth", "Intensity", "Elevation", "Azimuth",
  "K:F Ratio", "K:B Ratio", "K:H Ratio", nullptr };

// These are the same as vtkLightKitSubTypeStrings but shorter
// useful for a GUI with minimum space
static const char* vtkLightKitSubTypeShortStrings[] = { "War.", "Int. ", "Ele.", "Azi.", "K:F",
  "K:B", "K:H", nullptr };

//------------------------------------------------------------------------------
vtkLightKit::vtkLightKit()
{
  // create members
  this->KeyLight = vtkLight::New();
  this->FillLight = vtkLight::New();
  this->HeadLight = vtkLight::New();
  this->BackLight0 = vtkLight::New();
  this->BackLight1 = vtkLight::New();

  for (int i = 0; i < 4; i++)
  {
    this->WarmthFunction[i] = vtkPiecewiseFunction::New();
  }
  this->InitializeWarmthFunctions();

  // initialize values
  this->KeyLight->SetLightTypeToCameraLight();
  this->FillLight->SetLightTypeToCameraLight();
  this->BackLight0->SetLightTypeToCameraLight();
  this->BackLight1->SetLightTypeToCameraLight();

  this->HeadLight->SetLightTypeToHeadlight();

  this->SetKeyLightAngle(50.0, 10.0);
  this->SetFillLightAngle(-75.0, -10.0);
  this->SetBackLightAngle(0.0, 110.0);

  this->KeyLightWarmth = 0.6;
  this->FillLightWarmth = 0.4;
  this->HeadLightWarmth = 0.5;
  this->BackLightWarmth = 0.5;

  this->KeyLightIntensity = 0.75;
  this->KeyToFillRatio = 3.0;
  this->KeyToBackRatio = 3.5;
  this->KeyToHeadRatio = 3.0;

  this->MaintainLuminance = 0;
  this->Update();
}

//------------------------------------------------------------------------------
vtkLightKit::~vtkLightKit()
{
  this->KeyLight->Delete();
  this->FillLight->Delete();
  this->HeadLight->Delete();
  this->BackLight0->Delete();
  this->BackLight1->Delete();

  for (int i = 0; i < 4; i++)
  {
    this->WarmthFunction[i]->Delete();
  }
}

//------------------------------------------------------------------------------
void vtkLightKit::SetKeyLightAngle(double elevation, double azimuth)
{
  this->KeyLightAngle[0] = elevation;
  this->KeyLightAngle[1] = azimuth;

  this->KeyLight->SetDirectionAngle(elevation, azimuth);
}

//------------------------------------------------------------------------------
void vtkLightKit::SetFillLightAngle(double elevation, double azimuth)
{
  this->FillLightAngle[0] = elevation;
  this->FillLightAngle[1] = azimuth;

  this->FillLight->SetDirectionAngle(elevation, azimuth);
}

//------------------------------------------------------------------------------
void vtkLightKit::SetBackLightAngle(double elevation, double azimuth)
{
  this->BackLightAngle[0] = elevation;
  this->BackLightAngle[1] = azimuth;

  this->BackLight0->SetDirectionAngle(elevation, azimuth);
  this->BackLight1->SetDirectionAngle(elevation, -azimuth);
}

//------------------------------------------------------------------------------
void vtkLightKit::WarmthToRGB(double w, double rgb[3])
{
  rgb[0] = this->WarmthFunction[0]->GetValue(w);
  rgb[1] = this->WarmthFunction[1]->GetValue(w);
  rgb[2] = this->WarmthFunction[2]->GetValue(w);
}

//------------------------------------------------------------------------------
double vtkLightKit::WarmthToIntensity(double w)
{
  return this->WarmthFunction[3]->GetValue(w);
}

//------------------------------------------------------------------------------
void vtkLightKit::WarmthToRGBI(double w, double rgb[3], double& i)
{
  rgb[0] = this->WarmthFunction[0]->GetValue(w);
  rgb[1] = this->WarmthFunction[1]->GetValue(w);
  rgb[2] = this->WarmthFunction[2]->GetValue(w);
  i = this->WarmthFunction[3]->GetValue(w);
}

//------------------------------------------------------------------------------
void vtkLightKit::AddLightsToRenderer(vtkRenderer* renderer)
{
  if (renderer != nullptr)
  {
    renderer->AddLight(this->HeadLight);
    renderer->AddLight(this->KeyLight);
    renderer->AddLight(this->FillLight);
    renderer->AddLight(this->BackLight0);
    renderer->AddLight(this->BackLight1);
  }
}

//------------------------------------------------------------------------------
void vtkLightKit::RemoveLightsFromRenderer(vtkRenderer* renderer)
{
  if (renderer != nullptr)
  {
    renderer->RemoveLight(this->HeadLight);
    renderer->RemoveLight(this->KeyLight);
    renderer->RemoveLight(this->FillLight);
    renderer->RemoveLight(this->BackLight0);
    renderer->RemoveLight(this->BackLight1);
  }
}

//------------------------------------------------------------------------------
void vtkLightKit::Modified()
{
  this->Update();
  this->Superclass::Modified();
}

//------------------------------------------------------------------------------
void vtkLightKit::Update()
{
  double* fillLightColor = this->FillLightColor;
  double fillLightPI;

  double* keyLightColor = this->KeyLightColor;
  double keyLightPI;

  double* headlightColor = this->HeadLightColor;
  double headlightPI;

  double* backLightColor = this->BackLightColor;
  double backLightPI;

  double fillLightIntensity, keyLightIntensity, headlightIntensity;
  double backLightIntensity;

  this->WarmthToRGBI(this->KeyLightWarmth, keyLightColor, keyLightPI);
  this->WarmthToRGBI(this->FillLightWarmth, fillLightColor, fillLightPI);
  this->WarmthToRGBI(this->HeadLightWarmth, headlightColor, headlightPI);
  this->WarmthToRGBI(this->BackLightWarmth, backLightColor, backLightPI);

  keyLightIntensity = this->KeyLightIntensity;

  fillLightIntensity = keyLightIntensity / this->KeyToFillRatio;
  headlightIntensity = keyLightIntensity / this->KeyToHeadRatio;
  backLightIntensity = keyLightIntensity / this->KeyToBackRatio;

  // This is sort of interesting: the fill light intensity is weighted
  // by the perceptual brightness of the color of each light.  Since
  // the fill light will often be a cooler color than the key light,
  // the bluer color would otherwise seem less bright than the neutral
  // and this bias the key-to-fill ratio.  This factor compensates for
  // this problem.  Note we always do this correction, no matter what
  // the MaintainLuminance flag says.  That flag's for controlling
  // the intensity of the entire scene, not just the fill light.

  if (this->MaintainLuminance)
  {
    fillLightIntensity /= fillLightPI;
    headlightIntensity /= headlightPI;
    keyLightIntensity /= keyLightPI;
    backLightIntensity /= backLightPI;
  }
  this->KeyLight->SetColor(keyLightColor);
  this->KeyLight->SetIntensity(keyLightIntensity);

  this->FillLight->SetColor(fillLightColor);
  this->FillLight->SetIntensity(fillLightIntensity);

  this->HeadLight->SetColor(headlightColor);
  this->HeadLight->SetIntensity(headlightIntensity);

  this->BackLight0->SetColor(backLightColor);
  this->BackLight0->SetIntensity(backLightIntensity);

  this->BackLight1->SetColor(backLightColor);
  this->BackLight1->SetIntensity(backLightIntensity);
}

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

  os << indent << "KeyLightIntensity: " << this->KeyLightIntensity << "\n";
  os << indent << "KeyToFillRatio: " << this->KeyToFillRatio << "\n";
  os << indent << "KeyToHeadRatio: " << this->KeyToHeadRatio << "\n";
  os << indent << "KeyToBackRatio: " << this->KeyToBackRatio << "\n";

  os << indent << "KeyLightWarmth: " << this->KeyLightWarmth << "\n";
  os << indent << "KeyLightAngle: (" << this->KeyLightAngle[0] << ", " << this->KeyLightAngle[1]
     << ")\n";

  os << indent << "FillLightWarmth: " << this->FillLightWarmth << "\n";
  os << indent << "FillLightAngle: (" << this->FillLightAngle[0] << ", " << this->FillLightAngle[1]
     << ")\n";

  os << indent << "BackLightWarmth: " << this->BackLightWarmth << "\n";
  os << indent << "BackLightAngle: (" << this->BackLightAngle[0] << ", " << this->BackLightAngle[1]
     << ")\n";

  os << indent << "HeadLightWarmth: " << this->HeadLightWarmth << "\n";

  os << indent << "MaintainLuminance: " << (this->MaintainLuminance ? "On" : "Off") << "\n";

  // here, but commented out to satisfy validation tests....
  // os << indent << "KeyLightColor: ("
  //    << this->KeyLightColor[0] << ", "
  //    << this->KeyLightColor[1] << ", "
  //    << this->KeyLightColor[2] << ")\n";

  // os << indent << "FillLightColor: ("
  //    << this->FillLightColor[0] << ", "
  //    << this->FillLightColor[1] << ", "
  //    << this->FillLightColor[2] << ")\n";

  // os << indent << "HeadLightColor: ("
  //    << this->HeadLightColor[0] << ", "
  //    << this->HeadLightColor[1] << ", "
  //    << this->HeadLightColor[2] << ")\n";

  // os << indent << "BackLightColor: ("
  //    << this->BackLightColor[0] << ", "
  //    << this->BackLightColor[1] << ", "
  //    << this->BackLightColor[2] << ")\n";
}

//------------------------------------------------------------------------------
void vtkLightKit::DeepCopy(vtkLightKit* k)
{
  this->KeyLightIntensity = k->KeyLightIntensity;
  this->KeyToFillRatio = k->KeyToFillRatio;
  this->KeyToHeadRatio = k->KeyToHeadRatio;
  this->KeyToBackRatio = k->KeyToBackRatio;

  this->KeyLightWarmth = k->KeyLightWarmth;
  this->FillLightWarmth = k->FillLightWarmth;
  this->HeadLightWarmth = k->HeadLightWarmth;
  this->BackLightWarmth = k->BackLightWarmth;

  this->KeyLightAngle[0] = k->KeyLightAngle[0];
  this->KeyLightAngle[1] = k->KeyLightAngle[1];

  this->FillLightAngle[0] = k->FillLightAngle[0];
  this->FillLightAngle[1] = k->FillLightAngle[1];

  this->BackLightAngle[0] = k->BackLightAngle[0];
  this->BackLightAngle[1] = k->BackLightAngle[1];

  this->MaintainLuminance = k->MaintainLuminance;

  this->KeyLight->DeepCopy(k->KeyLight);
  this->FillLight->DeepCopy(k->FillLight);
  this->HeadLight->DeepCopy(k->HeadLight);
  this->BackLight0->DeepCopy(k->BackLight0);
  this->BackLight1->DeepCopy(k->BackLight1);
}

//------------------------------------------------------------------------------
// r, g, b, sqrt(color length)
static double warmthTable[] = {
  0.1674, 0.3065, 1.0000, 0.5865, //
  0.1798, 0.3204, 1.0000, 0.5965, //
  0.1935, 0.3352, 1.0000, 0.6071, //
  0.2083, 0.3511, 1.0000, 0.6184, //
  0.2245, 0.3679, 1.0000, 0.6302, //
  0.2422, 0.3859, 1.0000, 0.6426, //
  0.2614, 0.4050, 1.0000, 0.6556, //
  0.2822, 0.4252, 1.0000, 0.6693, //
  0.3049, 0.4467, 1.0000, 0.6837, //
  0.3293, 0.4695, 1.0000, 0.6986, //
  0.3557, 0.4935, 1.0000, 0.7142, //
  0.3841, 0.5188, 1.0000, 0.7303, //
  0.4144, 0.5454, 1.0000, 0.7470, //
  0.4468, 0.5731, 1.0000, 0.7642, //
  0.4811, 0.6020, 1.0000, 0.7818, //
  0.5173, 0.6320, 1.0000, 0.7998, //
  0.5551, 0.6628, 1.0000, 0.8179, //
  0.5943, 0.6942, 1.0000, 0.8362, //
  0.6346, 0.7261, 1.0000, 0.8544, //
  0.6756, 0.7581, 1.0000, 0.8724, //
  0.7168, 0.7898, 1.0000, 0.8899, //
  0.7575, 0.8209, 1.0000, 0.9068, //
  0.7972, 0.8508, 1.0000, 0.9229, //
  0.8351, 0.8791, 1.0000, 0.9379, //
  0.8705, 0.9054, 1.0000, 0.9517, //
  0.9026, 0.9290, 1.0000, 0.9640, //
  0.9308, 0.9497, 1.0000, 0.9746, //
  0.9546, 0.9671, 1.0000, 0.9834, //
  0.9734, 0.9808, 1.0000, 0.9903, //
  0.9872, 0.9907, 1.0000, 0.9954, //
  0.9958, 0.9970, 1.0000, 0.9985, //
  0.9996, 0.9997, 1.0000, 0.9999, //
  1.0000, 0.9999, 0.9996, 0.9999, //
  1.0000, 0.9988, 0.9958, 0.9994, //
  1.0000, 0.9964, 0.9871, 0.9982, //
  1.0000, 0.9925, 0.9730, 0.9962, //
  1.0000, 0.9869, 0.9532, 0.9935, //
  1.0000, 0.9796, 0.9275, 0.9898, //
  1.0000, 0.9705, 0.8959, 0.9853, //
  1.0000, 0.9595, 0.8584, 0.9798, //
  1.0000, 0.9466, 0.8150, 0.9734, //
  1.0000, 0.9317, 0.7660, 0.9660, //
  1.0000, 0.9147, 0.7116, 0.9576, //
  1.0000, 0.8956, 0.6522, 0.9482, //
  1.0000, 0.8742, 0.5881, 0.9377, //
  1.0000, 0.8506, 0.5199, 0.9261, //
  1.0000, 0.8247, 0.4483, 0.9134, //
  1.0000, 0.7964, 0.3739, 0.8995, //
  1.0000, 0.7656, 0.2975, 0.8845, //
  1.0000, 0.7324, 0.2201, 0.8683, //
  1.0000, 0.6965, 0.1426, 0.8509, //
  1.0000, 0.6580, 0.0662, 0.8323, //
  1.0000, 0.6179, 0.0000, 0.8134, //
  1.0000, 0.5832, 0.0000, 0.8008, //
  1.0000, 0.5453, 0.0000, 0.7868, //
  1.0000, 0.5042, 0.0000, 0.7713, //
  1.0000, 0.4595, 0.0000, 0.7541, //
  1.0000, 0.4111, 0.0000, 0.7350, //
  1.0000, 0.3588, 0.0000, 0.7139, //
  1.0000, 0.3025, 0.0000, 0.6904, //
  1.0000, 0.2423, 0.0000, 0.6643, //
  1.0000, 0.1782, 0.0000, 0.6353, //
  1.0000, 0.1104, 0.0000, 0.6032, //
  1.0000, 0.0396, 0.0000, 0.5677  //
};

//------------------------------------------------------------------------------
void vtkLightKit::InitializeWarmthFunctions()
{
  const int len = sizeof(warmthTable) / sizeof(double) / 4;

  for (int i = 0; i < 4; i++)
  {
    this->WarmthFunction[i]->BuildFunctionFromTable(0.0, 1.0, len, &warmthTable[i], 4);
  }
}

//------------------------------------------------------------------------------
const char* vtkLightKit::GetStringFromType(int type)
{
  static const int n = sizeof(vtkLightKitTypeStrings) / sizeof(char*);
  if (type < n)
  {
    return vtkLightKitTypeStrings[type];
  }
  else
  {
    return nullptr;
  }
}

//------------------------------------------------------------------------------
const char* vtkLightKit::GetStringFromSubType(int subtype)
{
  static const int n = sizeof(vtkLightKitSubTypeStrings) / sizeof(char*);
  if (subtype < n)
  {
    return vtkLightKitSubTypeStrings[subtype];
  }
  else
  {
    return nullptr;
  }
}

//------------------------------------------------------------------------------
const char* vtkLightKit::GetShortStringFromSubType(int subtype)
{
  static const int n = sizeof(vtkLightKitSubTypeShortStrings) / sizeof(char*);
  if (subtype < n)
  {
    return vtkLightKitSubTypeShortStrings[subtype];
  }
  else
  {
    return nullptr;
  }
}
//------------------------------------------------------------------------------
vtkLightKit::LightKitSubType vtkLightKit::GetSubType(vtkLightKit::LightKitType type, int i)
{
  // return subtype
  const LightKitSubType KeyLightSubType[4] = { Warmth, Intensity, Elevation, Azimuth };
  const LightKitSubType FillLightSubType[4] = { Warmth, KFRatio, Elevation, Azimuth };
  const LightKitSubType BackLightSubType[4] = { Warmth, KBRatio, Elevation, Azimuth };
  const LightKitSubType HeadLightSubType[2] = { Warmth, KHRatio };

  LightKitSubType subtype = Warmth; // please VS6
  switch (type)
  {
    case TKeyLight:
      subtype = KeyLightSubType[i];
      break;
    case TFillLight:
      subtype = FillLightSubType[i];
      break;
    case TBackLight:
      subtype = BackLightSubType[i];
      break;
    case THeadLight:
      subtype = HeadLightSubType[i];
      break;
  }

  return subtype;
}
