// Copyright (C) 1999-2015
// Smithsonian Astrophysical Observatory, Cambridge, MA, USA
// For conditions of distribution and use, see copyright notice in "copyright"

#include <fstream>

#include "colorbar.h"
#include "util.h"
#include "ps.h"

#include "lut.h"
#include "sao.h"
#include "default.h"

Colorbar::Colorbar(Tcl_Interp* i, Tk_Canvas c, Tk_Item* item) 
  : ColorbarBase(i,c,item)
{
  colorIndex = NULL;
  bias = .5;
  contrast = 1.0;

  tag =NULL;
  tagaction =NONE;
  taginit =0;
}

Colorbar::~Colorbar()
{
  if (colorIndex)
    delete [] colorIndex;
}

void Colorbar::adjustCmd(float c, float b)
{
  contrast = c;
  bias = b;
  updateColors();
}

void Colorbar::getBiasCmd()
{
  ostringstream str;
  str << bias << ends;
  Tcl_AppendResult(interp, str.str().c_str(), NULL);
}

void Colorbar::getColorbarCmd()
{
  if (cmaps.current()) {
    ostringstream str;
    str << cmaps.current()->getID() << ' '
      << bias << ' ' 
      << contrast << ' ' 
      << invert << ends;
    Tcl_AppendResult(interp, str.str().c_str(), NULL);
  }
  else
    result = TCL_ERROR;
}

void Colorbar::getColormapCmd()
{
  if (cmaps.current()) {
    ostringstream str;
    str << cmaps.current()->getID() << ' '
	<< bias << ' ' 
	<< contrast << ' ' 
	<< invert << ' '
	<< colorIndex << ' '
	<< (unsigned short*)colorCells << ' '
	<< colorCount << ends;
    Tcl_AppendResult(interp, str.str().c_str(), NULL);
  }
  else
    result = TCL_ERROR;
}

void Colorbar::getColormapNameCmd(int id)
{
  ColorMapInfo* ptr = cmaps.begin();
  while (ptr) {
    if (ptr->getID() == id) {
      Tcl_AppendResult(interp, (char*)ptr->getName(), NULL);
      return;
    }
    ptr = ptr->next();
  }

  // if we got this far, we did not find it, bail out
  Tcl_AppendResult(interp, " colormap not found.", NULL);
  result = TCL_ERROR;
}

void Colorbar::getColormapFileNameCmd(int id)
{
  ColorMapInfo* ptr = cmaps.begin();
  while (ptr) {
    if (ptr->getID() == id) {
      Tcl_AppendResult(interp, (char*)ptr->getFileName(), NULL);
      return;
    }
    ptr = ptr->next();
  }

  // if we got this far, we did not find it, bail out
  Tcl_AppendResult(interp, " colormap not found.", NULL);
  result = TCL_ERROR;
}

void Colorbar::getContrastCmd()
{
  ostringstream str;
  str << contrast << ends;
  Tcl_AppendResult(interp, str.str().c_str(), NULL);
}

void Colorbar::getCurrentIDCmd()
{
  if (cmaps.current()) {
    ostringstream str;
    str << cmaps.current()->getID() << ends;
    Tcl_AppendResult(interp, str.str().c_str(), NULL);
  }
  else
    result = TCL_ERROR;
}

void Colorbar::getCurrentNameCmd()
{
  if (cmaps.current())
    Tcl_AppendElement(interp, (char*)cmaps.current()->getName());
  else
    result = TCL_ERROR;
}

void Colorbar::getCurrentFileNameCmd()
{
  if (cmaps.current())
    Tcl_AppendElement(interp, (char*)cmaps.current()->getFileName());
  else
    result = TCL_ERROR;
}

void Colorbar::getRGBChannelCmd()
{
  Tcl_AppendResult(interp, "red", NULL);
}

void Colorbar::listIDCmd()
{

  ColorMapInfo* ptr = cmaps.begin();
  while (ptr) {
    ostringstream str;
    str << ptr->getID() << ends;
    Tcl_AppendElement(interp, str.str().c_str());

    ptr = ptr->next();
  }
}

void Colorbar::listNameCmd()
{
  ColorMapInfo* ptr = cmaps.begin();
  while (ptr) {
    Tcl_AppendElement(interp, (char*)ptr->getName());
    ptr = ptr->next();
  }
}

void Colorbar::loadCmd(const char* fn, const char* type)
{
  ColorMapInfo* map = newColorMap(fn, type);

  if (map && map->load()) {
    // add new colormap to end of the list
    cmaps.append(map);
    reset();
  }
  else {
    // something has gone wrong, clean up, and bail out
    delete map;
    Tcl_AppendResult(interp, " unable to load colormap: ", fn, NULL);
    result = TCL_ERROR;
  }
}

void Colorbar::loadCmd(const char* fn, const char* type, const char* var)
{
  ColorMapInfo* map = newColorMap(fn, type);

  if (map && map->load(var)) {
    // add new colormap to end of the list
    cmaps.append(map);
    reset();
  }
  else {
    // something has gone wrong, clean up, and bail out
    delete map;
    Tcl_AppendResult(interp, " unable to load colormap: ", fn, NULL);
    result = TCL_ERROR;
  }
}

void Colorbar::mapCmd(char* which)
{
  char* a = toLower(which);
  cmaps.head();
  do {
    char* b = toLower(cmaps.current()->getName());
    if (!strcmp(a,b)) {
      reset();
      delete [] a;
      delete [] b;
      return;
    }
    delete [] b;
  }
  while (cmaps.next());

  // if we got this far, we did not find it, bail out
  cmaps.head();
  delete [] a;
  result = TCL_ERROR;
}

void Colorbar::mapCmd(int id)
{
  cmaps.head();
  do {
    if (cmaps.current()->getID() == id) {
      reset();
      return;
    }
  }
  while (cmaps.next());

  // if we got this far, we did not find it, bail out
  cmaps.head();
  result = TCL_ERROR;
}

void Colorbar::saveCmd(const char* fn)
{
  cmaps.current()->save(fn);
}

void Colorbar::saveCmd(int id, const char* fn)
{
  ColorMapInfo* ptr = cmaps.begin();
  while (ptr) {
    if (ptr->getID() == id) {
      ptr->save(fn);
      return;
    }
    ptr = ptr->next();
  }

  result = TCL_ERROR;
}

void Colorbar::setColorbarCmd(int id, float b, float c, int i)
{
  cmaps.head();
  while (cmaps.current()) {
    if (cmaps.current()->getID() == id) {
      bias = b;
      contrast = c;
      invert = i;
      updateColors();
      return;
    }
    cmaps.next();
  }

  // if we got this far, we did not find it, bail out
  cmaps.head();
  result = TCL_ERROR;
}

void Colorbar::getTagCmd()
{
  ostringstream str;
  ctags.head();
  while (ctags.current()) {
    str << ctags.current()->start() << ' ' 
	<< ctags.current()->stop() << ' '
	<< ctags.current()->colorname() << ' ';
    ctags.next();
  }
  str << ends;
  Tcl_AppendResult(interp, str.str().c_str(), NULL);
}

void Colorbar::getTagCmd(int xx, int yy)
{
  ColorbarBaseOptions* opts = (ColorbarBaseOptions*)options;

  int rr,ss;
  if (!opts->orientation) {
    rr = xx;
    ss = float(rr)/opts->width * colorCount;
  }
  else {
    rr = yy;
    ss = (1-float(rr)/opts->height) * colorCount;
  }

  ctags.head();
  while (ctags.current()) {
    if (ss>ctags.current()->start() && ss<ctags.current()->stop()) {
      int startid = float(ctags.current()->start())/colorCount * cnt;
      int stopid = float(ctags.current()->stop())/colorCount * cnt;
      if (startid<0)
	startid = 0;
      if (startid>=cnt)
	startid = cnt-1;
      if (stopid<0)
	stopid = 0;
      if (stopid>=cnt)
	stopid = cnt-1;

      ostringstream str;
      str << ctags.current()->id() << ' ' 
	  << lut[startid] << ' '
	  << lut[stopid] << ' '
	  << ctags.current()->colorname() << ends;
      Tcl_AppendResult(interp, str.str().c_str(), NULL);
      return;
    }
    ctags.next();
  }
}

void Colorbar::tagCmd(const char* txt)
{
  ctags.deleteAll();

  istringstream str(txt);
  while (!str.eof()) {
    int aa =0;
    int bb =0;
    char color[32];
    *color ='\0';

    str >> aa >> bb >> color;

    if (aa && bb && *color)
      ctags.append(new ColorTag(this,aa,bb,color));
  }
  updateColors();
}

void Colorbar::tagCmd(int id, const Vector& v, const char* color)
{
  Vector vv = v;
  int mm=0;
  int nn=0;
  ctags.head();
  while (ctags.current()) {
    if (ctags.current()->id() == id) {
      // special case
      if (vv[0]>lut[cnt-1] && vv[1]>lut[cnt-1])
	return;
      else if (vv[0]<lut[0] && vv[1]<lut[0])
	return;

      mm =0;
      for (int ii=0; ii<cnt; ii++)
	if (vv[0]<lut[ii]) {
	  mm=ii;
	  break;
	}
      nn =cnt-1;
      for (int ii=cnt-1; ii>=0; ii--)
	if (vv[1]>lut[ii]) {
	  nn=ii;
	  break;
	}

      Vector rr = Vector(mm,nn)/cnt*colorCount;
      ctags.current()->set(rr[0],rr[1],color);
      updateColors();
      return;
    }
    ctags.next();
  }
}

void Colorbar::tagDeleteCmd()
{
  ctags.deleteAll();
  updateColors();
}

void Colorbar::tagDeleteCmd(int xx, int yy)
{
  ColorbarBaseOptions* opts = (ColorbarBaseOptions*)options;

  int rr,ss;
  if (!opts->orientation) {
    rr = xx;
    ss = float(rr)/opts->width * colorCount;
  }
  else {
    rr = yy;
    ss = (1-float(rr)/opts->height) * colorCount;
  }

  ctags.head();
  while (ctags.current()) {
    if (ss>ctags.current()->start() && ss<ctags.current()->stop()) {
      ColorTag* ct = ctags.extract();
      if (ct)
	delete ct;

      updateColors();
      return;
    }
    ctags.next();
  }
}

void Colorbar::tagEditBeginCmd(int xx, int yy, const char* color)
{
  ColorbarBaseOptions* opts = (ColorbarBaseOptions*)options;

  tag= NULL;
  tagaction =NONE;
  taginit =0;

  int rr,ss;
  if (!opts->orientation) {
    rr = xx;
    ss = float(rr)/opts->width * colorCount;
  }
  else {
    rr = yy;
    ss = (1-float(rr)/opts->height) * colorCount;
  }

  ctags.head();
  while (ctags.current()) {
    int start = ctags.current()->start();
    int stop = ctags.current()->stop();
    if (ss>start && ss<stop) {
      if (ss>stop-10 && ss<stop) {
	tagaction =STOP;
      }
      else if (ss>start && ss<start+10) {
	tagaction =START;
      }
      else {
	tagaction =MOVE;
      }
      tag = ctags.current();
      taginit =rr;
      return;
    }
    ctags.next();
  }

  // else create a new tag
  ctags.append(new ColorTag(this,ss,ss,color));
  tag = ctags.current();
  tagaction =CREATE;
  taginit =rr;
}

void Colorbar::tagEditMotionCmd(int xx, int yy)
{
  ColorbarBaseOptions* opts = (ColorbarBaseOptions*)options;

  if (tag) {
    int rr,ss,tt;
    if (!opts->orientation) {
      rr = xx;
      ss = float(rr)/opts->width * colorCount;
      tt = float(taginit)/opts->width *colorCount;
    }
    else {
      rr = yy;
      ss = (1-float(rr)/opts->height) * colorCount;
      tt = (1-float(taginit)/opts->height) *colorCount;
    }

    switch (tagaction) {
    case NONE:
      break;
    case CREATE:
      tagaction =STOP;
      tag->move(0,ss-tt);
      break;
    case START:
      tag->move(ss-tt,0);
      break;
    case STOP:
      tag->move(0,ss-tt);
      break;
    case MOVE:
      tag->move(ss-tt,ss-tt);
      break;
    }
    taginit = rr;

    updateColors();
  }
}

void Colorbar::tagEditEndCmd(int xx, int yy)
{
  ColorbarBaseOptions* opts = (ColorbarBaseOptions*)options;

  if (tag) {
    int rr,ss,tt;
    if (!opts->orientation) {
      rr = xx;
      ss = float(rr)/opts->width * colorCount;
      tt = float(taginit)/opts->width *colorCount;
    }
    else {
      rr = yy;
      ss = (1-float(rr)/opts->height) * colorCount;
      tt = (1-float(taginit)/opts->height) *colorCount;
    }

    switch (tagaction) {
    case NONE:
      break;
    case CREATE:
      tag->width(100);
      break;
    case START:
      break;
    case STOP:
      break;
    case MOVE:
      break;
    }

    updateColors();
  }
}

void Colorbar::tagLoadCmd(const char* fn)
{
  ifstream str(fn);

  if (str) {
    ctags.deleteAll();

    while (!str.eof()) {
      int mm=0;
      int nn=0;
      double aa =0;
      double bb =0;
      char color[32];
      *color ='\0';

      str >> aa >> bb >> color;

      if (aa && bb && *color) {
	// special case
	if (aa>lut[cnt-1] && bb>lut[cnt-1])
	  continue;
	else if (aa<lut[0] && bb<lut[0])
	  continue;

	mm =0;
	for (int ii=0; ii<cnt; ii++)
	  if (aa<lut[ii]) {
	    mm=ii;
	    break;
	  }
	nn =cnt-1;
	for (int ii=cnt-1; ii>=0; ii--)
	  if (bb>lut[ii]) {
	    nn=ii;
	    break;
	  }

	Vector rr = Vector(mm,nn)/cnt*colorCount;
	ctags.append(new ColorTag(this,rr[0],rr[1],color));
      }
    }

    updateColors();
  }
}

void Colorbar::tagSaveCmd(const char* fn)
{
  ofstream str(fn);
  if (str) {
    ctags.head();
    while (ctags.current()) {
      int startid = float(ctags.current()->start())/colorCount * cnt;
      int stopid = float(ctags.current()->stop())/colorCount * cnt;
      if (startid<0)
	startid = 0;
      if (startid>=cnt)
	startid = cnt-1;
      if (stopid<0)
	stopid = 0;
      if (stopid>=cnt)
	stopid = cnt-1;

      str << lut[startid] << ' ' << lut[stopid] << ' '
	  << ctags.current()->colorname() << endl;
      ctags.next();
    }
  }
}

// private

int Colorbar::calcContrastBias(int i)
{
  // if default (contrast = 1.0 && bias = .5) return
  if (fabs(bias - 0.5) < 0.0001 && fabs(contrast - 1.0) < 0.0001)
    return i;
  
  // map i to range of 0 to 1.0
  // shift by bias (if invert, bias = 1-bias)
  // multiply by contrast
  // shift to center of region
  // expand back to number of dynamic colors
  float b = invert ? 1-bias : bias;
  int r = (int)(((((float)i / colorCount) - b) * contrast + .5 ) * colorCount);

  // clip to bounds if out of range
  if (r < 0)
    return 0;
  else if (r >= colorCount)
    return colorCount-1;
  else
    return r;
}

void Colorbar::reset()
{
  bias = 0.5;
  contrast = 1.0;
  invert = 0;

  updateColors();
}

void Colorbar::loadDefaultCMaps()
{
  cmaps.append(new GreyColorMap(this));
  cmaps.append(new RedColorMap(this));
  cmaps.append(new GreenColorMap(this));
  cmaps.append(new BlueColorMap(this));
  cmaps.append(new AColorMap(this));
  cmaps.append(new BColorMap(this));
  cmaps.append(new BBColorMap(this));
  cmaps.append(new HEColorMap(this));
  cmaps.append(new I8ColorMap(this));
  cmaps.append(new AIPSColorMap(this));
  cmaps.append(new SLSColorMap(this));
  cmaps.append(new HSVColorMap(this));
  cmaps.append(new HeatColorMap(this));
  cmaps.append(new CoolColorMap(this));
  cmaps.append(new RainbowColorMap(this));
  cmaps.append(new StandardColorMap(this));
  cmaps.append(new StaircaseColorMap(this));
  cmaps.append(new ColorColorMap(this));

  cmaps.head();
}

ColorMapInfo* Colorbar::newColorMap(const char* fn, const char* type)
{
  // determine colormap type
  char* tmp = dupstr(fn);      // tmp memory, we will mangle it
  char* ptr = tmp;
  while (*ptr++);              // walk forward till end of string
  ptr--;                       // backup one
  while (ptr != tmp && *ptr != '.')  // march backward looking for '.'
    ptr--;
  if (ptr != tmp) {            // are we at '.' or start of string
    *ptr = '\0';               // mark end of string at '.'
    ptr++;
  }

  // Create ColorMap
  ColorMapInfo* map = NULL;

  // if type specified, use it, otherwise, use file extension
  if (type)
    ptr = (char*)type;

  if (strncmp(ptr, "lut", 3) == 0)
    map = new LUTColorMap(this);
  else
    map = new SAOColorMap(this);

  // Bail out if we don't have a new ColorMap
  if (!map)
    return NULL;

  // Extract a name from the file name. Any extension has already been removed.
  ptr = tmp;
  while (*ptr++);           // walk forward till end of string
  while (ptr != tmp && *ptr != '/')  // march backward looking for '/'
    ptr--;
  if (ptr != tmp)           // see if we found '/' or at begining of string
    ptr++;

  map->setName(ptr);
  map->setFileName(fn);

  delete [] tmp;               // clean up
  return map;
}

void Colorbar::psHorz(Filter& filter, int width, int height)
{
  for (int jj=0; jj<height; jj++) {
    ostringstream str;

    for (int ii=0; ii<width; ii++) {
      int kk = (int)(double(ii)/width*colorCount)*3;
      unsigned char red = colorCells[kk+2];
      unsigned char green = colorCells[kk+1];
      unsigned char blue = colorCells[kk];

      switch (psColorSpace) {
      case BW:
      case GRAY:
	filter << RGB2Gray(red, green, blue);
	break;
      case RGB:
	filter << red << green << blue;
	break;
      case CMYK:
	{
	  unsigned char cyan, magenta, yellow, black;
	  RGB2CMYK(red, green, blue, &cyan, &magenta, &yellow, &black);
	  filter << cyan << magenta << yellow << black;
	}
	break;
      }
      str << filter;
    }

    str << ends;
    psFix(str);
    Tcl_AppendResult(interp, str.str().c_str(), NULL);
  }
}

void Colorbar::psVert(Filter& filter, int width, int height)
{
  for (int jj=0; jj<height; jj++) {
    ostringstream str;

    int kk = (int)(double(jj)/height*colorCount)*3;
    unsigned char red = colorCells[kk+2];
    unsigned char green = colorCells[kk+1];
    unsigned char blue = colorCells[kk];

    switch (psColorSpace) {
    case BW:
    case GRAY:
      for (int ii=0; ii<width; ii++)
	filter << RGB2Gray(red, green, blue);
      break;
    case RGB:
      for (int ii=0; ii<width; ii++)
	filter << red << green << blue;
      break;
    case CMYK:
      for (int ii=0; ii<width; ii++) {
	unsigned char cyan, magenta, yellow, black;
	RGB2CMYK(red, green, blue, &cyan, &magenta, &yellow, &black);
	filter << cyan << magenta << yellow << black;
      }
      break;
    }

    str << filter << ends;
    psFix(str);
    Tcl_AppendResult(interp, str.str().c_str(), NULL);
  }
}

#ifdef MAC_OSX_TK
void Colorbar::macosx(float scale, int width, int height, 
		      const Vector& v, const Vector& s)
{
  if (!colorCells)
    return;

  // destination
  unsigned char* dst = new unsigned char[width*height*4];
  unsigned char* dptr = dst;

  if (!((ColorbarBaseOptions*)options)->orientation) {
    for (int jj=0; jj<height; jj++)
      for (int ii=0; ii<width; ii++) {
	int kk = (int)(double(ii)/width*colorCount)*3;
	*dptr++ = colorCells[kk+2];
	*dptr++ = colorCells[kk+1];
	*dptr++ = colorCells[kk];
	*dptr++ = 0;
      }
  }
  else {
    for (int jj=0; jj<height; jj++) {
      int kk = (int)(double(jj)/height*colorCount)*3;
      unsigned char rr = colorCells[kk+2];
      unsigned char gg = colorCells[kk+1];
      unsigned char bb = colorCells[kk];
      for (int ii=0; ii<width; ii++) {
	*dptr++ = rr;
	*dptr++ = gg;
	*dptr++ = bb;
	*dptr++ = 0;
      }
    }
  }

  macosxBitmapCreate(dst, width, height, v, s);

  if (dst)
    delete [] dst;
}
#endif

#ifdef __WIN32
void Colorbar::win32(float scale, int width, int height, 
		      const Vector& v, const Vector& s)
{
  if (!colorCells)
    return;

  // destination (width must be aligned on 4-byte DWORD boundary)
  int jjwidth=(((width+3)/4)*4);

  // extra alignment padding which we have to skip over for each row
  int jjpad=(jjwidth-width)*3;

  unsigned char* dst = new unsigned char[jjwidth*height*3];
  if (!dst)
    return;
  memset(dst, '\0', jjwidth*height*3);

  unsigned char* dptr = dst;

  if (!((ColorbarBaseOptions*)options)->orientation) {
    for (int jj=0; jj<height; jj++) {
      for (int ii=0; ii<width; ii++) {
	int kk = (int)(double(ii)/width*colorCount)*3;
	*dptr++ = colorCells[kk];
	*dptr++ = colorCells[kk+1];
	*dptr++ = colorCells[kk+2];
      }
      dptr += jjpad;
    }
  }
  else {
    for (int jj=0; jj<height; jj++) {
      int kk = (int)(double(jj)/height*colorCount)*3;
      unsigned char rr = colorCells[kk];
      unsigned char gg = colorCells[kk+1];
      unsigned char bb = colorCells[kk+2];
      for (int ii=0; ii<width; ii++) {
	*dptr++ = rr;
	*dptr++ = gg;
	*dptr++ = bb;
      }
      dptr += jjpad;
    }
  }

  win32Clip(v,s);
  win32BitmapCreate(dst, jjwidth, height, v, s);
  win32Clip(Vector(INT_MIN,INT_MIN),Vector(INT_MAX,INT_MAX));

  if (dst)
    delete [] dst;
}
#endif

