/*

  xmunipack - palettes


  Copyright © 1997-2011 F.Hroch (hroch@physics.muni.cz)

  This file is part of Munipack.

  Munipack 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.
  
  Munipack 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 Munipack.  If not, see <http://www.gnu.org/licenses/>.



  NEEDS DOCUMENTATION !!!!


*/


#include "fits.h"
#include <wx/wx.h>

using namespace std;


// ---- reference counting data base
class FitsPaletteData : public wxObjectRefData
{
public:
  FitsPaletteData();
  FitsPaletteData(const FitsPaletteData&);
  FitsPaletteData& operator = (const FitsPaletteData&);
  virtual ~FitsPaletteData();

  int npal, nlo, nhi;
  unsigned char *rpal,*gpal,*bpal;  
};



FitsPaletteData::FitsPaletteData(): npal(0),rpal(0),gpal(0),bpal(0) 
{
  npal = 3*256;
  nlo = 256;
  nhi = 256 + 256;
  rpal = new unsigned char[npal];
  gpal = new unsigned char[npal];
  bpal = new unsigned char[npal];
}


FitsPaletteData::FitsPaletteData(const FitsPaletteData& copy) 
{ 
  wxFAIL_MSG("FitsPaletteData WE ARE REALY NEED COPY CONSTRUCTOR");
}

FitsPaletteData& FitsPaletteData::operator = (const FitsPaletteData& other)
{
  wxFAIL_MSG("FitsPaletteData: WE ARE REALLY NEED ASSIGNMENT CONSTRUCTOR");
  return *this;
}


FitsPaletteData::~FitsPaletteData() 
{ 
  delete[] rpal;
  delete[] gpal;
  delete[] bpal;
}



// ---- FitsPalette

FitsPalette::FitsPalette(int n): pal(n), negative(false)
{
  UnRef();
  SetRefData(new FitsPaletteData);
  SetPalette(n);
}


FitsPalette::~FitsPalette() {}

void FitsPalette::SetNegative(bool n)
{
  negative = n;
  CreatePalette();
}

bool FitsPalette::GetNegative() const 
{ 
  return negative; 
}

int FitsPalette::GetPalette() const 
{ 
  return pal; 
}

wxString FitsPalette::GetPalette_str() const 
{ 
  return Type_str(pal);
}

int FitsPalette::GetColors() const
{
  FitsPaletteData *data = static_cast<FitsPaletteData *>(GetRefData());
  wxASSERT(data);
  return data->npal;
}

wxString FitsPalette::Type_str(int n)
{
  switch(n) {
  case PAL_GREY:   return "Gray";
  case PAL_SEPIA:  return "Sepia";
  case PAL_VGA:    return "VGA";
  case PAL_AIPS0:  return "AIPS0";
  case PAL_STAIR:  return "Staircase";
  case PAL_COLOR:  return "Color";
  case PAL_SAW:    return "Saw";
  case PAL_RAIN:   return "Rainbow";
  case PAL_MAD:    return "Madness";
  case PAL_COOL:   return "Cool";
  case PAL_HEAT:   return "Heat";
  case PAL_SPRING: return "Spring";
  case PAL_WRAP:   return "Wrap";

  case PAL_FIRST:
  case PAL_LAST:
  default: 
    wxFAIL_MSG("Undefined type of palette.");
    return wxEmptyString;
  } 
}

wxArrayString FitsPalette::Type_str()
{
  wxArrayString a;

  for(int i = PAL_FIRST+1; i < PAL_LAST; i++)
    a.Add(Type_str(i));
	  
  return a;
}


void FitsPalette::SetPalette(int l)
{
  if( PAL_FIRST < l && l < PAL_LAST ) 
    pal = l; 
  else 
    pal = PAL_GREY;
  
  CreatePalette();
}

void FitsPalette::CreatePalette()
{

  // VGA pallete (http://en.wikipedia.org/wiki/Web_colors)
  const int vga[16][3] = {
    {0,0,0},       // black #000000
    {0,0,128},     // navy, #000080
    {0,128,0},     // green, #008000
    {128,0,0},     // maroon, #800000
    {128,128,0},   // olive, #808000
    {0,128,128},   // teal, #008080
    {128,0,128},   // purple, #800080
    {128,128,128}, // gray, #808080
    {0,0,255},     // blue, #0000FF
    {0,255,255},   // aqua, #00FFFF
    {255,0,255},   // fuchsia, #FF00FF
    {0,255,0},     // lime, #00FF00
    {255,255,0},   // yelow, #FFFF00
    {255,0,0},     // red, #FF0000
    {192,192,192}, // silver , #C0C0C0
    {255,255,255}};// white, #FFFFFF


  // by ds9 
  const int aips0[9][3] = {
    {49,49,49},    
    {121,0,155},
    {0,0,200},
    {95,167,235},
    {0,151,0},
    {0,246,0},
    {255,255,0},
    {255,176,0},
    {255,0,0}};


  int n;

  FitsPaletteData *data = static_cast<FitsPaletteData *>(GetRefData());
  wxASSERT(data && data->npal > 0);
  int npal = data->npal;
  int nlo = data->nlo;
  int nhi = data->nhi;
  unsigned char *r = data->rpal;
  unsigned char *g = data->gpal;
  unsigned char *b = data->bpal;

  switch (pal) {
  case PAL_SEPIA: 

    for(int i = 0; i < nlo; i++ ) {
      r[i] = g[i] = b[i] = 0;
    }
    for(int i = nlo; i < nhi; i++ ) {
      int l = i - nlo;
      int k = l - 56; if( k < 0 ) k = 0;
      int m = k - 20; if( m < 0 ) m = 0;
      r[i] = l;
      g[i] = k;
      b[i] = m;
    }
    for(int i = nhi; i < npal; i++ ) {
      r[i] = r[i-1]; g[i] = g[i-1]; b[i] = b[i-1];
    }
    break;

  case PAL_VGA:
    
    for(int i = 0; i < nlo; i++ ) {
      r[i] = vga[0][0];
      g[i] = vga[0][1];
      b[i] = vga[0][2];
    }

    n = nlo;
    for(int i = 0; i < 16; i++ ) {
      for(int j = 0; j < 16; j++ ) {
	r[n] = vga[i][0];
	g[n] = vga[i][1];
	b[n] = vga[i][2];
	n++;
      }
    }

    for(int i = nhi; i < npal; i++ ) {
      r[i] = vga[15][0];
      g[i] = vga[15][1];
      b[i] = vga[15][2];
    }
    break;


  case PAL_AIPS0:


    for(int i = 0; i < nlo + 4; i++ )
      r[i] = g[i] = b[i] = 0;

    n = nlo + 4;
    for(int i = 0; i < 9; i++ ) {
      for(int j = 0; j < 28; j++ ) {
	r[n] = aips0[i][0];
	g[n] = aips0[i][1];
	b[n] = aips0[i][2];
	n++;
      }
    }

    for(int i = n; i < npal; i++ )
      r[i] = g[i] = b[i] = 255;

    break;

  case PAL_STAIR:

    for(int i = 0; i < nlo; i++ ) {
      r[i] = g[i] = b[i] = 0;
    }

    n = nlo;
    r[n] = 0;
    g[n] = 0;
    b[n] = 0;
    n++;
    for(int i = 0; i < 5; i++ ) {
      for(int j = 0; j < 17; j++) {
	r[n] = 0;
	g[n] = 0;
	b[n] = i*48 + 64;
	n++;
      }
    }
    for(int i = 5; i < 10; i++ ) {
      for(int j = 0; j < 17; j++ ) {
	r[n] = 0;
	g[n] = (i-5)*48 + 64;
	b[n] = 0;
	n++;
      }
    }
    for(int i = 10; i < 15; i++ ) {
      for(int j = 0; j < 17; j++ ) {
	r[n] = (i-10)*48 + 64;
	g[n] = 0;
	b[n] = 0;
	n++;
      }
    }

    for(int i = nhi; i < npal; i++ ) {
      r[i] = g[i] = b[i] = 0;
    }

    break;

  case PAL_COLOR: 

    for(int i = 0; i < nlo; i++)
      r[i] = g[i] = b[i] = i;


    for(int i = nlo; i < npal; i++ ) {
      float x = 3.14*(i - nlo)/(npal - nlo);
      g[i] = int(255.0*sin(x));
      if( i > (nlo + npal)/2.0 ) {
	r[i] = int(255.0*cos(3.14+x));
	b[i] = 0;
      }
      else {
	r[i] = 0;
	b[i] = int(255.0*cos(x));
      }
    }


    // !!!!!
    // An experimanetal implementation of color table generaded in Luv
    // Important:
    //   * tempareature must be set to get gray images for u,v = 0
    //   * hue is angle around white point
    //   * chroma sets distance from white point
    //   * the full covered table decrease contrast
    //   * the best contrast (details) are visible for luts with many colors
    //   * elegantly generates luts with black and white tails
   

//     for(int i = 0; i < npal; i++) {

//       float L = 100.0*i/float(npal);
//       float u = 0.0;
//       float v = 0.0;
//       float gr = float(i)/float(npal);

//       FitsColor color;
//       color.SetTemperatures(1.0,1.0,1.0);
//       float X,Y,Z;

//       //      color.sRGB_XYZ(gr,gr,gr,X,Y,Z);
      
//       X = 0.412381*gr +  0.357573*gr  + 0.180452*gr;
//       Y = 0.212620*gr +  0.715139*gr  + 0.072150*gr;
//       Z = 0.019343*gr +  0.119212*gr  + 0.950507*gr;

//       X = Y = Z = gr;
//       //      color.XYZ_Luv(X,Y,Z,1.0,L,u,v);
      


//       //      wxLogDebug(_("%f %f %f %f %f %f"),X,Y,Z,L,u,v);

//       //      L = 50;
//       //      u = (u - 0.211);
//       //      v = (v - 0.475);
//       float hue = atan2f(v,u);
//       //      u = 13.0*L*(u - 0.2);
//       //      v = 13.0*L*(v - 0.46);
//       float chroma = sqrtf(v*v + u*u);

//       //      float chroma = 100.0*float(i)/float(npal);
//       //      chroma = float(i)/float(npal);
      
//       if( i < npal/2 ) 
// 	chroma = i/float(npal);
//       else
// 	chroma = float(npal - i)/float(npal);
      
//       //chroma = sinf(3.14*i/float(npal));
//       //      chroma = 0.5*100;
//       //      float hue = 6.28*i/float(npal);//255.0;
//       hue = 12*6.28*i/float(npal) + 4.3;
//       hue = 4.3;
//       chroma = chroma*L*4.0;
//       //      chroma = 150.0;
//       //      L = 55;

//       u = chroma*cosf(hue);
//       v = chroma*sinf(hue);

//       //      u = 100;
//       //      v = 100;

//       //u = 13*(u-0.25)*L;
//       //      v = 13*(v-0.46)*L;
//       wxLogDebug(_("%f %f %f %f %f"),hue,chroma,L,u,v);
//       //      u = u/13.0/L+0.19784;
//       //      v = v/13.0/L+0.46831;
//       //      u = 0.211;
//       //      v = 0.475;

//       color.Luv_XYZ(L,u,v,1.0,X,Y,Z);
//       color.XYZ_sRGB(X,Y,Z,r[i],g[i],b[i]);

//       /*
//       int l = i; if( l > 255 ) l = 128;
//       int k = l - 128; if( k < 0 ) k = 0;
//       int m = k - 128; if( m < 0 ) m = 0; 
//       r[i] = l;
//       g[i] = k;
//       b[i] = m;
//       */
//     }


    break;

  case PAL_SAW: 

    for(int i = 0; i < nlo; i++ ) {
      r[i] = g[i] = 0; b[i] = i;
    }
    for(int i = nlo; i < nhi; i++ ) {
      r[i] = 0; g[i] = i - nlo; b[i] = 0;
    }
    for(int i = nhi; i < npal; i++ ) {
      r[i] = i - nhi; g[i] = b[i] = 0;
    }

    break;

  case PAL_RAIN: 

    for(int i = 0; i < npal; i++ ) {
      float x = 3.14*i/npal;
      g[i] = int(255.0*sin(x));
      if( i > npal/2.0 ) {
	r[i] = int(255.0*cos(3.14+x));
	b[i] = 0;
      }
      else {
	r[i] = 0;
	b[i] = int(255.0*cos(x));
      }
    }

    break;


  case PAL_MAD: 

    for(int i = 0; i < npal; i++) {
      float l = nhi - nlo;
      float x = 3.33*(i - nlo);
      r[i] = int(255.0*cos(3.14*(l+x)/l));
      g[i] = int(255.0*sin(3.14*x/l));
      b[i] = int(255.0*cos(3.14*x/l));      
    }
    break;

  case PAL_COOL: 

    for(int i = 0; i < nlo; i++ )
      r[i] = g[i] = b[i] = 0;

    for(int i = nlo; i < nhi; i++ ) { 
      int l = i - nlo;
      r[i] = l;
      g[i] = min(int(l*1.2),255);
      b[i] = min(int(l*2.0),255);
    }

    for(int i = nhi; i < npal; i++ )
      r[i] = g[i] = b[i] = 255;

    break;

  case PAL_HEAT:

    for(int i = 0; i < nlo; i++ )
      r[i] = g[i] = b[i] = 0;

    for(int i = nlo; i < nhi; i++ ) { 
      int l = i - nlo;
      r[i] = min(int(l*1.9),255);
      g[i] = min(int(l*1.1),255);
      b[i] = l;
    }

    for(int i = nhi; i < npal; i++ )
      r[i] = g[i] = b[i] = 255;

    break;

  case PAL_SPRING:

    for(int i = 0; i < nlo; i++ )
      r[i] = g[i] = b[i] = 0;

    for(int i = nlo; i < nhi; i++ ) { 
      int l = i - nlo;
      r[i] = l;
      g[i] = min(int(l*1.9),255);
      b[i] = l;
    }

    for(int i = nhi; i < npal; i++ )
      r[i] = g[i] = b[i] = 255;

    break;

  case PAL_WRAP:

    for(int i = 0; i < npal; i++ )
      r[i] = g[i] = b[i] = i % 256;
    break;

  case PAL_FIRST:
  case PAL_LAST:
  default:
    // grayscale

    for(int i = 0; i < nlo; i++ )
      r[i] = g[i] = b[i] = 0;

    for(int i = nlo; i < nhi; i++ )
      r[i] = g[i] = b[i] = i - nlo;

    for(int i = nhi; i < npal; i++ )
      r[i] = g[i] = b[i] = 255;

    break;
  }


  if( negative ) {

    FitsPaletteData *data = static_cast<FitsPaletteData *>(GetRefData());
    wxASSERT(data);

    int npal = data->npal;
    unsigned char *rpal = data->rpal;
    unsigned char *gpal = data->gpal;
    unsigned char *bpal = data->bpal;
    for(int i = 0; i < npal/2; i++) {
      unsigned char x;
      x = rpal[i]; rpal[i] = rpal[npal - i - 1]; rpal[npal - i -1] = x;
      x = gpal[i]; gpal[i] = gpal[npal - i - 1]; gpal[npal - i -1] = x;
      x = bpal[i]; bpal[i] = bpal[npal - i - 1]; bpal[npal - i -1] = x;
    }
  }

}

void FitsPalette::SetPalette(const wxString& a)
{
  for(int i = PAL_FIRST+1; i < PAL_LAST; i++)
    if( a == Type_str(i) ) {
      SetPalette(i);
      return;
    }
}

unsigned char FitsPalette::R(int i) const
{ 
  FitsPaletteData *data = static_cast<FitsPaletteData *>(GetRefData());
  wxASSERT(data && 0 <= i && i < data->npal);
  return data->rpal[i];
}

unsigned char FitsPalette::G(int i) const
{ 
  FitsPaletteData *data = static_cast<FitsPaletteData *>(GetRefData());
  wxASSERT(data && 0 <= i && i < data->npal);
  return data->gpal[i];
}

unsigned char FitsPalette::B(int i) const
{ 
  FitsPaletteData *data = static_cast<FitsPaletteData *>(GetRefData());
  wxASSERT(data && 0 <= i && i < data->npal);
  return data->bpal[i];
}

void FitsPalette::RGB(float f, unsigned char& r, unsigned char& g, 
		      unsigned char& b)
{
  FitsPaletteData *data = static_cast<FitsPaletteData *>(GetRefData());
  wxASSERT(data);

  int i = int(255.0*f + 1.0) + 256;

  if( i < 0 ) i = 0;
  if( i >= data->npal ) i = data->npal - 1;

  r = data->rpal[i];
  g = data->gpal[i];
  b = data->bpal[i];
}

unsigned char *FitsPalette::RGB(int n, float *f)
{
  FitsPaletteData *data = static_cast<FitsPaletteData *>(GetRefData());
  wxASSERT(data);

  unsigned char *rgb = (unsigned char *) malloc(3*n);
  unsigned char *p = rgb;
  int npal1 = data->npal - 1;

  for(int i = 0; i < n; i++) {

    int l = int(255.0f * *f++) + 256;       // rounding ommited

    if( l < 0 ) 
      l = 0;
    else if( l > npal1 ) 
      l = npal1;

    *p++ = data->rpal[l];
    *p++ = data->gpal[l];
    *p++ = data->bpal[l];
  }
  return rgb;
}

const unsigned char *FitsPalette::RData() const
{ 
  FitsPaletteData *data = static_cast<FitsPaletteData *>(GetRefData());
  wxASSERT(data);
  return data->rpal;
}

const unsigned char *FitsPalette::GData() const
{ 
  FitsPaletteData *data = static_cast<FitsPaletteData *>(GetRefData());
  wxASSERT(data);
  return data->gpal;
}

const unsigned char *FitsPalette::BData() const
{ 
  FitsPaletteData *data = static_cast<FitsPaletteData *>(GetRefData());
  wxASSERT(data);
  return data->bpal;
}
