// 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 "framergbtruecolor.h"
#include "fitsimage.h"
#include "ps.h"

#include "sigbus.h"

FrameRGBTrueColor::FrameRGBTrueColor(Tcl_Interp* i, Tk_Canvas c, Tk_Item* item)
  : FrameBase(i,c,item), FrameRGB(i,c,item), FrameTrue(i,c,item)
{
  for (int kk=0; kk<3; kk++)
    colormapData[kk] = NULL;
}

FrameRGBTrueColor::~FrameRGBTrueColor()
{
  for (int kk=0; kk<3; kk++)
    if (colormapData[kk])
      delete [] colormapData[kk];
}

void FrameRGBTrueColor::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;
  }
}

// Commands

void FrameRGBTrueColor::colormapCmd(float rb, float gb, float bb, 
				    float rc, float gc, float bc, int i,
				    unsigned char* cells, int cnt)
{
  bias[0] = rb;
  bias[1] = gb;
  bias[2] = bb;
  contrast[0] = rc;
  contrast[1] = gc;
  contrast[2] = bc;
  invert = i;

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

void FrameRGBTrueColor::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[0].fits && !context[1].fits && !context[2].fits)
    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
  for (int kk=0; kk<3; kk++) {
    if (colormapData[kk])
      delete [] colormapData[kk];
    colormapData[kk] = new long[width*height];

    if (!colormapData[kk]) {
      internalError("Unable to alloc tmp data array");
      return;
    }
  }

  SETSIGBUS

  // fill data array
  for (int kk=0; kk<3; kk++) {
    if (!view[kk] || !context[kk].fits)
      continue;

    // basics
    int length = colorScale[kk]->size() - 1;

    FitsImage* sptr = context[kk].cfits;
    int mosaic = context[kk].isMosaic();

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

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

    // main loop
    long* dest = colormapData[kk];

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

	if (mosaic) {
	  sptr = context[kk].cfits;

	  mm = sptr->matrixToData(Coord::WIDGET).mm();
	  params = sptr->getDataParams(context[kk].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 = length;
	      else
		*dest = (int)(((value - ll)/diff * length) + .5);
	    }
	    else
	      *dest = -1; // nan

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

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

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

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

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

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

  for (int kk=0; kk<3; kk++)
    if (colormapData[kk]) {
      delete [] colormapData[kk];
      colormapData[kk] = NULL;
    }

  update(BASE); // always update
}

void FrameRGBTrueColor::colormapMotionCmd(float rb, float gb, float bb, 
					  float rc, float gc, float bc, int i,
					  unsigned char* cells, int cnt)
{
  // we need a colorScale before we can render
  if (!validColorScale())
    return;

  // first check for change
  if (bias[0] == rb && bias[1] == gb && bias[2] == bb && 
      contrast[0] == rc && contrast[1] == gc && contrast[2] == bc &&
      invert == i && colorCells)
    return;

  // we got a change
  bias[0] = rb;
  bias[1] = gb;
  bias[2] = bb;
  contrast[0] = rc;
  contrast[1] = gc;
  contrast[2] = bc;
  invert = i;

  updateColorCells(cells, cnt);
  updateColorScale();

  // special case
  if ((!view[0] || !context[0].fits) &&
      (!view[1] || !context[1].fits) &&
      (!view[2] || !context[2].fits))
    return;

  int& width = colormapXM->width;
  int& height = colormapXM->height;

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

  for (int kk=0; kk<3; kk++) {
    if (!view[kk] || !context[kk].fits)
      continue;

    const unsigned char* table = colorScale[kk]->psColors();
    long* src = colormapData[kk];
    unsigned char* dest = img;
    char* mptr = mk;
    for (long jj=0; jj<height; jj++)
      for (long ii=0; ii<width; ii++, src++, dest+=3, mptr++)
	if (*src >= 0) {
	  memcpy(dest+kk, table+(*src), 1);
	  *mptr = 2;
	}
	else if (*src == -1 && *mptr < 2)
	  *mptr = 1;
  }

  // set remainder to bg
  {
    unsigned char* dest = img;
    char* mptr = mk;
    for (long jj=0; jj<height; jj++)
      for (long ii=0; ii<width; ii++, dest+=3, mptr++)
	if (*mptr == 2) // good value
	  ;
	else if (*mptr == 1) { // nan
	  *(dest  ) = (unsigned char)nanColor->red;
	  *(dest+1) = (unsigned char)nanColor->green;
	  *(dest+2) = (unsigned char)nanColor->blue;
	} 
	else { // bg
	  *(dest  ) = (unsigned char)bgColor->red;
	  *(dest+1) = (unsigned char)bgColor->green;
	  *(dest+2) = (unsigned char)bgColor->blue;
	}
  }

  // build colormapXM
  encodeTrueColor((unsigned char*)img, colormapXM);

  // clean up
  if (img)
    delete [] img;
  if (mk)
    delete [] mk;

  // XImage to Pixmap
  XPutImage(display, colormapPM, Widget::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();
}

