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

  Program:   ITK-SNAP
  Module:    $RCSfile: ColorMap.cxx,v $
  Language:  C++
  Date:      $Date: 2009/09/21 21:54:21 $
  Version:   $Revision: 1.9 $
  Copyright (c) 2007 Paul A. Yushkevich
  
  This file is part of ITK-SNAP 

  ITK-SNAP is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
 
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

  -----

  Copyright (c) 2003 Insight Software Consortium. All rights reserved.
  See ITKCopyright.txt or http://www.itk.org/HTML/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 notices for more information. 

=========================================================================*/
#include "ColorMap.h"
#include "IRISException.h"
#include <algorithm>

bool
ColorMap::CMPoint
::operator==(const ColorMap::CMPoint& rhs) const
{
  if (m_Index != rhs.m_Index)
    return false;
  if (m_Type != rhs.m_Type)
    return false;
  for (int i = 0; i < 2; ++i)
    {
    for (int j = 0; j < 4; ++j)
      {
      if (m_RGBA[i][j] != rhs.m_RGBA[i][j])
        return false;
      }
    }
  return true;
}

ColorMap::CMPoint
::CMPoint()
{
  m_Index = 0.0;
  m_Type = CONTINUOUS;

  m_RGBA[0][0] = 0;  m_RGBA[1][0] = 0;
  m_RGBA[0][1] = 0;  m_RGBA[1][1] = 0;
  m_RGBA[0][2] = 0;  m_RGBA[1][2] = 0;
  m_RGBA[0][3] = 0;  m_RGBA[1][3] = 0;
  }

// Continuous point
ColorMap::CMPoint
::CMPoint(double j, EltType r, EltType g, EltType b, EltType a)
{
  m_Index = j;
  m_Type = CONTINUOUS;

  m_RGBA[0][0] = r;  m_RGBA[1][0] = r;
  m_RGBA[0][1] = g;  m_RGBA[1][1] = g;
  m_RGBA[0][2] = b;  m_RGBA[1][2] = b;
  m_RGBA[0][3] = a;  m_RGBA[1][3] = a;
}

// CMPoint with alpha discontinuity
ColorMap::CMPoint
::CMPoint(double j, EltType r, EltType g, EltType b, EltType a1, EltType a2)
{
  m_Index = j;
  m_Type = DISCONTINUOUS;

  m_RGBA[0][0] = r;  m_RGBA[1][0] = r;
  m_RGBA[0][1] = g;  m_RGBA[1][1] = g;
  m_RGBA[0][2] = b;  m_RGBA[1][2] = b;
  m_RGBA[0][3] = a1; m_RGBA[1][3] = a2;
}

// CMPoint with full discontinuity
ColorMap::CMPoint
::CMPoint(double j, 
    EltType r1, EltType g1, EltType b1, EltType a1,
    EltType r2, EltType g2, EltType b2, EltType a2)
{
  m_Index = j;
  m_Type = DISCONTINUOUS;

  m_RGBA[0][0] = r1; m_RGBA[1][0] = r2;
  m_RGBA[0][1] = g1; m_RGBA[1][1] = g2;
  m_RGBA[0][2] = b1; m_RGBA[1][2] = b2;
  m_RGBA[0][3] = a1; m_RGBA[1][3] = a2;
}

// CMPoint copy constructor
ColorMap::CMPoint
::CMPoint(const CMPoint& rhs)
{
  m_Index = rhs.m_Index;
  m_Type = rhs.m_Type;
  for (int i = 0; i < 2; ++i)
    {
    for (int j = 0; j < 4; ++j)
      {
      m_RGBA[i][j] = rhs.m_RGBA[i][j];
      }
    }
}

bool operator < (const ColorMap::CMPoint &p1, const ColorMap::CMPoint &p2)
{
  return p1.m_Index < p2.m_Index;
}

ColorMap
::ColorMap()
{
  SetToSystemPreset(COLORMAP_GREY);
}

ColorMap
::ColorMap(SystemPreset preset)
{
  SetToSystemPreset(preset);
}

ColorMap
::ColorMap(const ColorMap& rhs)
{
  CMPointConstIterator it = rhs.m_CMPoints.begin();
  m_CMPreset = rhs.m_CMPreset;
  while (it != rhs.m_CMPoints.end())
    {
    m_CMPoints.push_back( *it );
    ++it;
    }
  this->UpdateInterpolants(); 
}

bool
ColorMap
::operator==(const ColorMap& rhs) const
{
  if (m_CMPoints.size() != rhs.m_CMPoints.size())
    return false;
  CMPointConstIterator it = m_CMPoints.begin();
  CMPointConstIterator rit = rhs.m_CMPoints.begin();
  while (it != m_CMPoints.end())
    {
    if (!(*it == *rit))
      return false;
    ++it;
    ++rit;
    }
  return true;
}

size_t
ColorMap
::InsertInterpolatedCMPoint(double j)
{
  // Create the new point
  RGBAType rgba = MapIndexToRGBA(j);
  CMPoint newbie(j, rgba[0], rgba[1], rgba[2], rgba[3]);

  // Find where to insert the new point
  CMPointIterator it = 
    std::lower_bound(m_CMPoints.begin(), m_CMPoints.end(), newbie);

  // Insert the point after the lower bound
  CMPointIterator itnew = m_CMPoints.insert(it, newbie);

  this->UpdateInterpolants(); 

  return itnew - m_CMPoints.begin();
}


void
ColorMap
::UpdateInterpolants()
{
  // Set the size of the interpolant array to n+1
  size_t n = m_CMPoints.size();
  m_Interpolants.resize(n+1);

  // Loop over the component
  for(size_t d = 0; d < 4; d++)
    {
    // Set first and last interpolants
    m_Interpolants[0].slope[d] = 0.0f; 
    m_Interpolants[0].intercept[d] = m_CMPoints[0].m_RGBA[0][d];
    m_Interpolants[n].slope[d] = 0.0f; 
    m_Interpolants[n].intercept[d] = m_CMPoints[n-1].m_RGBA[1][d];

    // Set the intermediate interpolants
    for(size_t i = 1; i < n; i++)
      {
      float t0 = m_CMPoints[i-1].m_Index;
      float t1 = m_CMPoints[i].m_Index;
      float c0 = m_CMPoints[i-1].m_RGBA[1][d];
      float c1 = m_CMPoints[i].m_RGBA[0][d];
      if(t1 > t0)
        {
        m_Interpolants[i].slope[d] = (c1 - c0) / (t1 - t0);
        m_Interpolants[i].intercept[d] = c0 - m_Interpolants[i].slope[d] * t0;
        }
      else
        {
        m_Interpolants[i].slope[d] = 0;
        m_Interpolants[i].intercept[d] = c0;
        }
      }
    }
}

ColorMap::RGBAType
ColorMap
::MapIndexToRGBA(double j) const
{
  // We use simple linear search because most colormaps
  // are tiny and the overhead of binary search is not worth it
  size_t n = m_CMPoints.size(), lb;
  for(lb = 0; lb < n; lb++)
    if(j < m_CMPoints[lb].m_Index)
      break;

  // Get the interpolants
  const InterpolantData &ic = m_Interpolants[lb];

  // Compute the output value
  RGBAType c;
  c[0] = (unsigned char)(ic.intercept[0] + ic.slope[0] * j);
  c[1] = (unsigned char)(ic.intercept[1] + ic.slope[1] * j);
  c[2] = (unsigned char)(ic.intercept[2] + ic.slope[2] * j);
  c[3] = (unsigned char)(ic.intercept[3] + ic.slope[3] * j);
  return c;
}

void
ColorMap
::PrintSelf()
{
  for(size_t i = 0; i < m_CMPoints.size(); i++)
    {
    CMPoint p = m_CMPoints[i];
    if(p.m_Type == CONTINUOUS)
      {
      printf("%02d-C %7.2f   (%03d %03d %03d %03d)\n", (int) i, p.m_Index, p.m_RGBA[0][0], p.m_RGBA[0][1], p.m_RGBA[0][2], p.m_RGBA[0][3]);
      }
    else
      {
      printf("%02d-L %7.2f   (%03d %03d %03d %03d)\n", (int) i, p.m_Index, p.m_RGBA[0][0], p.m_RGBA[0][1], p.m_RGBA[0][2], p.m_RGBA[0][3]);
      printf("%02d-R %7.2f   (%03d %03d %03d %03d)\n", (int) i, p.m_Index, p.m_RGBA[1][0], p.m_RGBA[1][1], p.m_RGBA[1][2], p.m_RGBA[1][3]);
      }
    }
}
void
ColorMap
::SetToSystemPreset(SystemPreset preset)
{
  m_CMPoints.clear();
  m_CMPreset = preset;
  switch(preset)
  {
    case COLORMAP_GREY:
      m_CMPoints.push_back( CMPoint(0.0, 0x00, 0x00, 0x00, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(1.0, 0xff, 0xff, 0xff, 0xff, 0x00) );
      break;

    case COLORMAP_RED:
      m_CMPoints.push_back( CMPoint(0.0, 0x00, 0x00, 0x00, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(1.0, 0xff, 0x00, 0x00, 0xff, 0x00) );
      break;

    case COLORMAP_GREEN:
      m_CMPoints.push_back( CMPoint(0.0, 0x00, 0x00, 0x00, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(1.0, 0x00, 0xff, 0x00, 0xff, 0x00) );
      break;

    case COLORMAP_BLUE:
      m_CMPoints.push_back( CMPoint(0.0, 0x00, 0x00, 0x00, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(1.0, 0x00, 0x00, 0xff, 0xff, 0x00) );
      break;

    case COLORMAP_HOT:
      m_CMPoints.push_back( CMPoint(0.0        , 0x00, 0x00, 0x00, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint( 2.0 / 63.0, 0x00, 0x00, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(22.0 / 63.0, 0xd8, 0x00, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(28.0 / 63.0, 0xff, 0x3a, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(48.0 / 63.0, 0xff, 0xff, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(1.0        , 0xff, 0xff, 0xff, 0xff, 0x00) );
      break;

    case COLORMAP_COOL:
      m_CMPoints.push_back( CMPoint(0.0, 0xff, 0x00, 0xff, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(1.0, 0x00, 0xff, 0xff, 0xff, 0x00) );
      break;

    case COLORMAP_SPRING:
      m_CMPoints.push_back( CMPoint(0.0, 0xff, 0x00, 0xff, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(1.0, 0xff, 0xff, 0x00, 0xff, 0x00) );
      break;

    case COLORMAP_SUMMER:
      m_CMPoints.push_back( CMPoint(0.0, 0x00, 0x80, 0x66, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(1.0, 0xff, 0xff, 0x66, 0xff, 0x00) );
      break;

    case COLORMAP_AUTUMN:
      m_CMPoints.push_back( CMPoint(0.0, 0xff, 0x00, 0x00, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(1.0, 0xff, 0xff, 0x00, 0xff, 0x00) );
      break;

    case COLORMAP_WINTER:
      m_CMPoints.push_back( CMPoint(0.0, 0x00, 0x00, 0xff, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(1.0, 0x00, 0xff, 0x80, 0xff, 0x00) );
      break;

    case COLORMAP_COPPER:
      m_CMPoints.push_back( CMPoint(0.0,    0x00, 0x00, 0xff, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(0.8334, 0xff, 0xaa, 0x6a, 0xff) );
      m_CMPoints.push_back( CMPoint(1.0,    0xff, 0xcc, 0x80, 0xff, 0x00) );
      break;

    case COLORMAP_HSV:
      m_CMPoints.push_back( CMPoint(0.0,    0xff, 0x00, 0x00, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(0.1667, 0xff, 0xff, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(0.3334, 0x00, 0xff, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(0.5000, 0x00, 0xff, 0xff, 0xff) );
      m_CMPoints.push_back( CMPoint(0.6667, 0x00, 0x00, 0xff, 0xff) );
      m_CMPoints.push_back( CMPoint(0.8334, 0xff, 0x00, 0xff, 0xff) );
      m_CMPoints.push_back( CMPoint(1.0,    0xff, 0x00, 0x00, 0xff, 0x00) );
      break;

    case COLORMAP_JET:
      m_CMPoints.push_back( CMPoint(0.0,    0x00, 0x00, 0x80, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(0.1   , 0x00, 0x00, 0xff, 0xff) );
      m_CMPoints.push_back( CMPoint(0.36  , 0x00, 0xff, 0xff, 0xff) );
      m_CMPoints.push_back( CMPoint(0.6   , 0xff, 0xff, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(0.9   , 0xff, 0x00, 0x00, 0xff) );
      m_CMPoints.push_back( CMPoint(1.0,    0x80, 0x00, 0x00, 0xff, 0x00) );
      break;

    case COLORMAP_BWR:
      m_CMPoints.push_back( CMPoint(0.0,    0x00, 0x00, 0xff, 0x00, 0xff) ); 
      m_CMPoints.push_back( CMPoint(0.5,    0xff, 0xff, 0xff, 0xff) ); 
      m_CMPoints.push_back( CMPoint(1.0,    0xff, 0x00, 0x00, 0xff, 0x00) );
      break;

    case COLORMAP_RWB:
      m_CMPoints.push_back( CMPoint(0.0,    0xff, 0x00, 0x00, 0x00, 0xff) ); 
      m_CMPoints.push_back( CMPoint(0.5,    0xff, 0xff, 0xff, 0xff) ); 
      m_CMPoints.push_back( CMPoint(1.0,    0x00, 0x00, 0xff, 0xff, 0x00) );
      break;

    case COLORMAP_SIZE:
      // to suppress compiler warning
      std::cerr << "COLORMAP_SIZE: should never get there ..." << std::endl;
      break;
   }

  this->UpdateInterpolants(); 
}

void ColorMap
::SaveToRegistry(Registry &reg)
{
  // Store the number of control points
  reg["NumberOfControlPoints"] << m_CMPoints.size();

  RegistryEnumMap<CMPointType> emap;
  emap.AddPair(CONTINUOUS,"Continuous");
  emap.AddPair(DISCONTINUOUS,"Discontinuous");

  // Save each control point
  for(size_t iPoint = 0; iPoint < m_CMPoints.size(); iPoint++)
    {
    // Get the current values, just in case
    CMPoint p = m_CMPoints[iPoint];

    // Create a folder in the registry
    std::string key = reg.Key("ControlPoint[%04d]",iPoint);
    Registry &folder = reg.Folder(key);
    folder["Index"] << p.m_Index;
    folder["Type"].PutEnum(emap, p.m_Type);
    folder["Left.R"] << (int) p.m_RGBA[0][0];
    folder["Left.G"] << (int) p.m_RGBA[0][1];
    folder["Left.B"] << (int) p.m_RGBA[0][2];
    folder["Left.A"] << (int) p.m_RGBA[0][3];
    folder["Right.R"] << (int) p.m_RGBA[1][0];
    folder["Right.G"] << (int) p.m_RGBA[1][1];
    folder["Right.B"] << (int) p.m_RGBA[1][2];
    folder["Right.A"] << (int) p.m_RGBA[1][3];
    }
}


 void ColorMap
::LoadFromRegistry(Registry &reg)
{
  // Store the number of control points
  size_t n = reg["NumberOfControlPoints"][0];
  if(n == 0)
    throw IRISException("Can not read color map. No entries found");

  // Read each control point
  CMPointList newpts;

  RegistryEnumMap<CMPointType> emap;
  emap.AddPair(CONTINUOUS,"Continuous");
  emap.AddPair(DISCONTINUOUS,"Discontinuous");

  // Save each control point
  for(size_t iPoint = 0; iPoint < n; iPoint++)
    {
    // Get the current values, just in case
    CMPoint p;

    // Create a folder in the registry
    std::string key = reg.Key("ControlPoint[%04d]",iPoint);
    Registry &folder = reg.Folder(key);

    p.m_Index = folder["Index"][-1.0];
    p.m_Type = folder["Type"].GetEnum(emap, CONTINUOUS);
    p.m_RGBA[0][0] = (unsigned char) folder["Left.R"][0];
    p.m_RGBA[0][1] = (unsigned char) folder["Left.G"][0];
    p.m_RGBA[0][2] = (unsigned char) folder["Left.B"][0];
    p.m_RGBA[0][3] = (unsigned char) folder["Left.A"][0];
    if(p.m_Type == CONTINUOUS)
      {
      p.m_RGBA[1][0] = p.m_RGBA[0][0];
      p.m_RGBA[1][1] = p.m_RGBA[0][1];
      p.m_RGBA[1][2] = p.m_RGBA[0][2];
      p.m_RGBA[1][3] = p.m_RGBA[0][3];
      }
    else
      {
      p.m_RGBA[1][0] = (unsigned char) folder["Right.R"][0];
      p.m_RGBA[1][1] = (unsigned char) folder["Right.G"][0];
      p.m_RGBA[1][2] = (unsigned char) folder["Right.B"][0];
      p.m_RGBA[1][3] = (unsigned char) folder["Right.A"][0];
      }

    // Check validity
    if(iPoint == 0 && p.m_Index != 0.0)
      throw IRISException("Can not read color map. First point has non-zero index.");

    if(iPoint == n-1 && p.m_Index != 1.0)
      throw IRISException("Can not read color map. Last point has index not equal to 1.");

    if(iPoint > 0 && p.m_Index < newpts[iPoint-1].m_Index)
      throw IRISException("Can not read color map. Indices are not stored in order.");

    newpts.push_back(p);
    }

  // Got this far? store the new map
  m_CMPoints = newpts;
  this->UpdateInterpolants();
}
