/*

  xmunipack - palettes


  Copyright © 1997-2011, 2018-20 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/>.

*/


#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), inverse(false)
{
  UnRef();
  SetRefData(new FitsPaletteData);
  SetPalette(n);
}


FitsPalette::~FitsPalette() {}

void FitsPalette::SetInverse(bool n)
{
  inverse = n;
  CreatePalette();
}

bool FitsPalette::GetInverse() const
{
  return inverse;
}

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:
    // https://en.wikipedia.org/wiki/Sepia_(color)

    for(int i = 0; i < nlo; i++ ) {
      r[i] = g[i] = b[i] = 0;
    }
    for(int i = nlo; i < nhi; i++ ) {
      r[i] = min((3*112*(i-nlo))/256,255);
      g[i] = min((3* 66*(i-nlo))/256,255);
      b[i] = min((3* 20*(i-nlo))/256,255);
    }
    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 Lab
    // Important:
    //   * 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

// #define SQR(x) (x)*(x)

//     for(int i = 0; i < npal; i++) {
//       float L = 100.0 * float(i) / float(npal);
//       float hue = 6.28* L / 100.0;
//       hue = 45.0 / 57.3;
//       float c = 50*cos(3.14*(L - 50)/100.0);
//       //	c = 50*exp(-SQR((L-50)/10));
//       //	c = 0;
//       float La = c*cos(hue);
//       float Lb = c*sin(hue);

//       float X,Y,Z,R,G,B;
//       FitsColor color;
//       color.Lab_XYZ(L,La,Lb,&X,&Y,&Z);
//       color.XYZ_sRGB(X,Y,Z,&R,&G,&B);
//       if( false && (R < 0 || G < 0 || B < 0) ) {
// 	r[i] = 0;
// 	g[i] = 0;
// 	b[i] = 0;
//       }
//       else {
// 	r[i] = min(max(int(nhi*R),0),255);
// 	g[i] = min(max(int(nhi*G),0),255);
// 	b[i] = min(max(int(nhi*B),0),255);
//       }
//       r[i] = int(npal*R);
//       g[i] = int(npal*G);
//       b[i] = int(npal*B);
//       wxLogDebug("%d %f %f %f",i,L,La,Lb);
//     }

    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( inverse ) {

    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;
      int l = npal - i -1;
      x = rpal[i]; rpal[i] = rpal[l]; rpal[l] = x;
      x = gpal[i]; gpal[i] = gpal[l]; gpal[l] = x;
      x = bpal[i]; bpal[i] = bpal[l]; bpal[l] = 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;
    }
}

void FitsPalette::Reset()
{
  pal = PAL_GREY;
  inverse = false;
  CreatePalette();
}

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 l = max(min(int(255.0*f + 0.5)+256,data->npal-1),0);
  r = data->rpal[l];
  g = data->gpal[l];
  b = data->bpal[l];
}

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

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

  for(long i = 0; i < n; i++) {
    int l = max(min(int(255.0f * f[i] + 0.5f) + 256,m),0);
    *p++ = data->rpal[l];
    *p++ = data->gpal[l];
    *p++ = data->bpal[l];
  }
  return rgb;
}
