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

#include <tk.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "frametruecolor.h"
#include "fitsimage.h"
#include "ps.h"

#include "sigbus.h"

FrameTrueColor::FrameTrueColor(Tcl_Interp* i, Tk_Canvas c, Tk_Item* item)
  : FrameBase(i,c,item), Frame(i,c,item), FrameTrue(i,c,item)
{
  colormapData =NULL;
}

FrameTrueColor::~FrameTrueColor()
{
  if (colormapData)
    delete [] colormapData;
}

unsigned char* FrameTrueColor::blend(unsigned char* src, unsigned char* msk,
				     int width, int height)
{
  unsigned char* sptr = src; // 3 component
  unsigned char* mptr = msk; // 4 component, premultiplied

  for (int jj=0; jj<height; jj++)
    for (int ii=0; ii<width; ii++) {
      if (*(mptr+3)) {
	float aa = 1-maskAlpha;
	*sptr++ = *mptr++ + *sptr * aa;
	*sptr++ = *mptr++ + *sptr * aa;
	*sptr++ = *mptr++ + *sptr * aa;
	mptr++;
      }
      else {
	mptr+=4;
	sptr+=3;
      }
    }

  return src;
}

void FrameTrueColor::buildXImage(XImage* ximage, Coord::InternalSystem sys)
{
  // we need a colorScale before we can render
  if (!validColorScale())
    return;

  unsigned char* img = fillImage(ximage->width, ximage->height, sys);
  if (img) {
    encodeTrueColor(img, ximage);
    delete [] img;
  }
}

void FrameTrueColor::colormapCmd(int id, float b, float c, int i, 
				 unsigned short* index, unsigned char* cells, 
				 int cnt)
{
  cmapID = id;
  bias = b;
  contrast = c;
  invert = i;

  updateColorCells(index, cells, cnt);
  updateColorScale();
  update(BASE);
}

void FrameTrueColor::colormapBeginCmd()
{
  // we need a colorScale before we can render
  if (!validColorScale())
    return;

  // we need some fits data
  // we assume the colorScale length will not change during motion calls
  if (!context->cfits)
    return;

  int width = options->width;
  int height = options->height;

  // Create XImage
  if (!(colormapXM = XGetImage(display, pixmap, 0, 0, 
			       width, height, AllPlanes, ZPixmap))) {
    internalError("Unable to Create Colormap XImage");
    return;
  }

  // Create Pixmap
  colormapPM = 
    Tk_GetPixmap(display, Tk_WindowId(tkwin), width, height, depth);
  if (!colormapPM) {
    internalError("Unable to Create Colormap Pixmap");
    return;
  }

  // colormapGCXOR
  colormapGCXOR = XCreateGC(display, Tk_WindowId(tkwin), 0, NULL);

  // Create table index array
  if (colormapData)
    delete [] colormapData;
  colormapData = new long[width*height];
  if (!colormapData) {
    internalError("Unable to alloc tmp data array");
    return;
  }

  // fill data array
  // basics
  int bytesPerPixel = colormapXM->bits_per_pixel/8;

  int length = colorScale->size() - 1;
  int last = length * bytesPerPixel;

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

  long* dest = colormapData;

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

  double ll = sptr->getLowDouble();
  double hh = sptr->getHighDouble();
  double diff = hh - ll;

  // main loop

  SETSIGBUS
  for (long jj=0; jj<height; jj++) {
    for (long ii=0; ii<width; ii++, dest++) {
      // default is bg
      *dest = -2; 

      if (mosaic) {
	sptr = context->cfits;

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

	ll = sptr->getLowDouble();
	hh = sptr->getHighDouble();
	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(value)) {
	    if (value <= ll)
	      *dest = 0;
	    else if (value >= hh)
	      *dest = last;
	    else
	      *dest = (int)(((value - ll)/diff * length) + .5)*bytesPerPixel;
	  }
	  else
	    *dest = -1;

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

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

	      ll = sptr->getLowDouble();
	      hh = sptr->getHighDouble();
	      diff = hh - ll;
	    }
	  }
	}
      }
      while (mosaic && sptr);
    }
  }
  CLEARSIGBUS
}

void FrameTrueColor::colormapMotionCmd(int id, float b, float c, int i, 
				       unsigned short* index, 
				       unsigned char* cells, int cnt)
{
  // we need a colorScale before we can render
  if (!validColorScale())
    return;

  // first check for change
  if (cmapID == id && bias == b && contrast == c && invert == i && colorCells)
    return;

  // we got a change
  cmapID = id;
  bias = b;
  contrast = c;
  invert = i;

  updateColorCells(index, cells, cnt);
  updateColorScale();

  // if we have no data, stop now
  if (!context->cfits) 
    return;

  // clear ximage
  int& width = colormapXM->width;
  int& height = colormapXM->height;
  char* data = colormapXM->data;
  int bytesPerPixel = colormapXM->bits_per_pixel/8;
  int& bytesPerLine = colormapXM->bytes_per_line;

  const unsigned char* table = colorScale->colors();

  long* src = colormapData;
  for (long jj=0; jj<height; jj++) {
    // line may be padded at end
    char* dest = data + jj*bytesPerLine;

    for (long ii=0; ii<width; ii++, src++, dest+=bytesPerPixel)
      switch (*src) {
      case -1:
	memcpy(dest, nanTrueColor_, bytesPerPixel);
	break;
      case -2:
	memcpy(dest, bgTrueColor_, bytesPerPixel);
	break;
      default:
	memcpy(dest, table+(*src), bytesPerPixel);
	break;
      }
  }

  // XImage to Pixmap
  XPutImage(display, colormapPM, gc, colormapXM, 0, 0, 0, 0, width, height);

  // Display Pixmap
  Vector dd = Vector() * widgetToWindow;
  XCopyArea(display, colormapPM, Tk_WindowId(tkwin), colormapGCXOR, 0, 0, 
	    width, height, dd[0], dd[1]);

  // update panner
  updatePanner();
}

void FrameTrueColor::colormapEndCmd()
{
  if (colormapXM) {
    XDestroyImage(colormapXM);
    colormapXM = NULL;
  }

  if (colormapPM) {
    Tk_FreePixmap(display, colormapPM);
    colormapPM = 0;
  }

  if (colormapGCXOR) {
    XFreeGC(display, colormapGCXOR);
    colormapGCXOR = 0;
  }

  if (colormapData) {
    delete [] colormapData;
    colormapData = NULL;
  }

  update(BASE); // always update
}

unsigned char* FrameTrueColor::fillImage(int width, int height, 
					 Coord::InternalSystem sys)
{
  unsigned char* img = Frame::fillImage(width, height, sys);

  if (img) {
    if (context->mask.head()) {
      FitsMask* mptr = context->mask.tail();
      while (mptr) {
	unsigned char* msk = fillMask(mptr, width, height, sys);
	blend(img,msk,width,height);
	delete [] msk;
	mptr = mptr->previous();
      }
    }
  }

  return img;
}

unsigned char* FrameTrueColor::fillMask(FitsMask* msk, int width, int height,
					Coord::InternalSystem sys)
{
  FitsImage* currentMsk = msk->current();
  XColor* maskColor = msk->color();
  int mark = msk->mark();

  // img
  unsigned char* img = new unsigned char[width*height*4];
  memset(img,0,width*height*4);

  if (!currentMsk)
    return img;

  // basics
  FitsImage* sptr = currentMsk;
  int mosaic = isMosaic();

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

  // main loop
  unsigned char* dest = img;

  SETSIGBUS
  for (long jj=0; jj<height; jj++) {
    for (long ii=0; ii<width; ii++, dest+=4) {

      if (mosaic) {
	sptr = currentMsk;

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

      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) {
	  int value = sptr->getValueMask(long(yy)*srcw + long(xx));
       
	  if ((mark && value) || (!mark && !value)) {
	    *dest = ((unsigned char)maskColor->red)*maskAlpha;
	    *(dest+1) = ((unsigned char)maskColor->green)*maskAlpha;
	    *(dest+2) = ((unsigned char)maskColor->blue)*maskAlpha;
	    *(dest+3) = 1;
	  }

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

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

  return img;
}

void FrameTrueColor::pushMatrices()
{
  Base::pushMatrices();

  // alway identity
  Matrix rgbToRef; 

  // now any masks
  FitsMask* msk = currentContext->mask.tail();
  while (msk) {
    FitsImage* mskimg = msk->mask();
    while (mskimg) {
      FitsImage* sptr = mskimg;
      while (sptr) {
	sptr->updateMatrices(rgbToRef, refToWidget, widgetToCanvas);
	sptr = sptr->nextSlice();
      }
      mskimg = mskimg->nextMosaic();
    }

    msk = msk->previous();
  }
}

void FrameTrueColor::pushMagnifierMatrices()
{
  Base::pushMagnifierMatrices();

  FitsMask* msk = context->mask.tail();
  while (msk) {
    FitsImage* mskimg = msk->mask();
    while (mskimg) {
      FitsImage* sptr = mskimg;
      while (sptr) {
	sptr->updateMagnifierMatrices(refToMagnifier);
	sptr = sptr->nextSlice();
      }
      mskimg = mskimg->nextMosaic();
    }
    msk = msk->previous();
  }
}

void FrameTrueColor::pushPannerMatrices()
{
  Base::pushPannerMatrices();

  FitsMask* msk = context->mask.tail();
  while (msk) {
    FitsImage* mskimg = msk->mask();
    while (mskimg) {
      FitsImage* sptr = mskimg;
      while (sptr) {
	sptr->updatePannerMatrices(refToPanner);
	sptr = sptr->nextSlice();
      }
      mskimg = mskimg->nextMosaic();
    }
    msk = msk->previous();
  }
}

void FrameTrueColor::pushPSMatrices(float scale, int width, int height)
{
  Base::pushPSMatrices(scale, width, height);

  Matrix mx = psMatrix(scale, width, height);
  FitsMask* msk = context->mask.tail();
  while (msk) {
    FitsImage* ptr = msk->current();
    while (ptr) {
      ptr->updatePS(mx);
      ptr = ptr->nextMosaic();
    }
    msk = msk->previous();
  }
}

