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

#include "frame.h"
#include "fitsimage.h"
#include "ps.h"

#include "sigbus.h"

// Frame Member Functions

Frame::Frame(Tcl_Interp* i, Tk_Canvas c, Tk_Item* item) 
  : FrameBase(i,c,item)
{
  context = new Context();
  context->parent(this);

  currentContext = context;
  keyContext = context;
  keyContextSet =1;
  
  cmapID = 1;
  bias = 0.5;
  contrast = 1.0;

  colorCount = 0;
  colorScale = NULL;
  colorCells = NULL;
  indexCells = NULL;
}

Frame::~Frame()
{
  if (context)
    delete context;

  if (colorScale)
    delete colorScale;

  if (colorCells)
    delete [] colorCells;

  if (indexCells)
    delete [] indexCells;
}

unsigned char* Frame::fillImage(int width, int height, 
				Coord::InternalSystem sys)
{
  // img
  unsigned char* img = new unsigned char[width*height*3];
  {
    unsigned char* ptr = img;
    for (int jj=0; jj<height; jj++)
      for (int ii=0; ii<width; ii++) {
	*ptr++ = (unsigned char)bgColor->red;
	*ptr++ = (unsigned char)bgColor->green;
	*ptr++ = (unsigned char)bgColor->blue;
      }	
  }

  if (!context->cfits)
    return img;

  // basics
  int length = colorScale->size() - 1;
  const unsigned char* table = colorScale->psColors();

  FitsImage* sptr = context->cfits;
  int mosaic = isMosaic();

  // variable
  double* mm = sptr->matrixToData(sys).mm();
  FitsBound* params = sptr->getDataParams(context->frScale.scanMode());
  int srcw = sptr->width();

  double ll = sptr->low();
  double hh = sptr->high();
  double diff = hh - ll;

  // main loop
  unsigned char* dest = img;

  SETSIGBUS
  for (long jj=0; jj<height; jj++) {
    for (long ii=0; ii<width; ii++, dest+=3) {
      if (mosaic) {
	sptr = context->cfits;

	mm = sptr->matrixToData(sys).mm();
	params = sptr->getDataParams(context->frScale.scanMode());
	srcw = sptr->width();

	ll = sptr->low();
	hh = sptr->high();
	diff = hh - ll;
      }

      do {
	double xx = ii*mm[0] + jj*mm[3] + mm[6];
	double yy = ii*mm[1] + jj*mm[4] + mm[7];

	if (xx>=params->xmin && xx<params->xmax && 
	    yy>=params->ymin && yy<params->ymax) {
	  double value = sptr->getValueDouble(long(yy)*srcw + long(xx));

	  if (isfinite(diff) && isfinite(value)) {
	    if (value <= ll) {
	      *(dest+2) = table[0];
	      *(dest+1) = table[1];
	      *dest = table[2];
	    }
	    else if (value >= hh) {
	      *(dest+2) = table[length*3];
	      *(dest+1) = table[length*3+1];
	      *dest = table[length*3+2];
	    }
	    else {
	      int l = (int)(((value - ll)/diff * length) + .5);
	      *(dest+2) = table[l*3];
	      *(dest+1) = table[l*3+1];
	      *dest = table[l*3+2];
	    }
	  }
	  else {
	    *(dest+2) = nanColor->blue;
	    *(dest+1) = nanColor->green;
	    *dest = nanColor->red;
	  }

	  break;
	}
	else {
	  if (mosaic) {
	    sptr = sptr->nextMosaic();

	    if (sptr) {
	      mm = sptr->matrixToData(sys).mm();
	      params = sptr->getDataParams(context->frScale.scanMode());
	      srcw = sptr->width();

	      ll = sptr->low();
	      hh = sptr->high();
	      diff = hh - ll;
	    }
	  }
	}
      }
      while (mosaic && sptr);
    }
  }
  CLEARSIGBUS

  return img;
}

int Frame::isIIS() 
{
  return context->cfits && context->cfits->isIIS();
}

void Frame::reset()
{
  cmapID = 1;
  bias = 0.5;
  contrast = 1.0;
  context->frScale.resetScanMode();
  context->updateClip();
  
  Base::reset();
}

void Frame::updateColorCells(unsigned short* index, 
			     unsigned char* cells, int cnt)
{
  // the colorbar widget will pass us a pointer to the indexCells

  colorCount = cnt;
  if (indexCells)
    delete [] indexCells;
  indexCells = new unsigned short[cnt];
  if (!indexCells) {
    internalError("Unable to Alloc indexCells");
    return;
  }
  memcpy(indexCells, index, cnt*sizeof(unsigned short));

  // copy the rgb vales to the colorCells array (for postscript printing)

  if (colorCells)
    delete [] colorCells;
  colorCells = new unsigned char[cnt*3];
  if (!colorCells) {
    internalError("Unable to Alloc colorCells");
    return;
  }
  memcpy(colorCells, cells, cnt*3);
}

void Frame::unloadFits()
{
  if (DebugPerf)
    cerr << "Frame::unloadFits()" << endl;

  // clean up from iis if needed
  if (isIIS()) {
    context->frScale.setClipMode(FrScale::MINMAX);
    context->frScale.setMMMode(FrScale::SCAN);
    context->frScale.setULow(DEFAULTLOW);
    context->frScale.setUHigh(DEFAULTHIGH);
    context->frScale.setColorScaleType(FrScale::LINEARSCALE);
  }

  context->unload();

  FrameBase::unloadFits();
}

// Commands

void Frame::getColorbarCmd()
{
  ostringstream str;
  str << cmapID << ' ' << bias << ' ' << contrast << ' ' << invert << ends;
  Tcl_AppendResult(interp, str.str().c_str(), NULL);
}

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

void Frame::getRGBViewCmd()
{
  Tcl_AppendResult(interp, "1 1 1", NULL);
}

void Frame::getRGBSystemCmd()
{
  Tcl_AppendResult(interp, "image", NULL);
}

void Frame::getTypeCmd()
{
  Tcl_AppendResult(interp, "base", NULL);
}

void Frame::iisCmd(int width, int height)
{
  unloadAllFits();

  context->frScale.setClipMode(FrScale::USERCLIP);
  context->frScale.setMMMode(FrScale::SCAN);
  context->frScale.setULow(0);
  context->frScale.setUHigh(IISSIZE);
  context->frScale.setColorScaleType(FrScale::IISSCALE);

  FitsImage* img = new FitsImageIIS(currentContext, interp, width, height);

  loadDone(currentContext->load(ALLOC, "", img, IMG),IMG);
}

void Frame::iisEraseCmd()
{
  if (context->cfits)
    ((FitsImageIIS*)context->cfits)->iisErase();
}

void Frame::iisGetCmd(char* dest, int xx, int yy, int dx, int dy)
{
  if (context->cfits) {
    char* buf = ((FitsImageIIS*)context->cfits)->iisGet(xx,yy,dx,dy);
    memcpy(dest, buf, dx*dy);
    delete [] buf;
  }
}

void Frame::iisSetCmd(const char* src, int xx, int yy, int dx, int dy)
{
  if (context->cfits)
    ((FitsImageIIS*)context->cfits)->iisSet(src, xx, yy, dx, dy);
}

void Frame::iisWCSCmd(const Matrix& mx, const Vector& z, int zt)
{
  if (context->cfits)
    ((FitsImageIIS*)context->cfits)->iisWCS(mx, z, zt);
}

void Frame::savePhotoCmd(const char* ph)
{
  FitsImage* fits = currentContext->cfits;
  if (!fits)
    return;

  // basics
  int length = colorScale->size() - 1;
  const unsigned char* table = colorScale->psColors();

  // variable
  FitsBound* params = fits->getDataParams(context->frScale.scanMode());
  double ll = fits->low();
  double hh = fits->high();
  double diff = hh - ll;

  int width = params->xmax - params->xmin;
  int height = params->ymax - params->ymin;

  // photo
  if (*ph == '\0') {
    Tcl_AppendResult(interp, "bad image name ", NULL);
    return;
  }
  Tk_PhotoHandle photo = Tk_FindPhoto(interp, ph);
  if (!photo) {
    Tcl_AppendResult(interp, "bad image handle ", NULL);
    return;
  }
  if (Tk_PhotoSetSize(interp, photo, width, height) != TCL_OK) {
    Tcl_AppendResult(interp, "bad photo set size ", NULL);
    return;
  }    
  Tk_PhotoBlank(photo);
  Tk_PhotoImageBlock block;
  if (!Tk_PhotoGetImage(photo,&block)) {
    Tcl_AppendResult(interp, "bad image block ", NULL);
    return;
  }

  if (block.pixelSize<4) {
    Tcl_AppendResult(interp, "bad pixel size ", NULL);
    return;
  }

  // main loop
  SETSIGBUS
  unsigned char* dest = block.pixelPtr;
  for (long jj=params->ymax-1; jj>=params->ymin; jj--) {
    for (long ii=params->xmin; ii<params->xmax; ii++, dest += block.pixelSize) {
      double value = fits->getValueDouble(Vector(ii,jj));

      if (isfinite(diff) && isfinite(value)) {
	if (value <= ll) {
	  *(dest+block.offset[0]) = table[2];
	  *(dest+block.offset[1]) = table[1];
	  *(dest+block.offset[2]) = table[0];
	  *(dest+block.offset[3]) = 255;
	}
	else if (value >= hh) {
	  *(dest+block.offset[0]) = table[length*3+2];
	  *(dest+block.offset[1]) = table[length*3+1];
	  *(dest+block.offset[2]) = table[length*3];
	  *(dest+block.offset[3]) = 255;
	}
	else {
	  int l = (int)(((value - ll)/diff * length) + .5);
	  *(dest+block.offset[0]) = table[l*3+2];
	  *(dest+block.offset[1]) = table[l*3+1];
	  *(dest+block.offset[2]) = table[l*3];
	  *(dest+block.offset[3]) = 255;
	}
      }
      else {
	*(dest+block.offset[0]) = nanColor->red;
	*(dest+block.offset[1]) = nanColor->green;
	*(dest+block.offset[2]) = nanColor->blue;
	*(dest+block.offset[3]) = 255;
      }
    }
  }
  CLEARSIGBUS

  if (Tk_PhotoPutBlock(interp, photo, &block, 0, 0, width, height, 
			TK_PHOTO_COMPOSITE_SET) != TCL_OK) {
    Tcl_AppendResult(interp, "bad put block ", NULL);
    return;
  }
}



