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

#include "context.h"
#include "fitsimage.h"
#include "fvcontour.h"

#include "alloc.h"
#include "allocgz.h"
#include "channel.h"
#include "mmap.h"
#include "mmapincr.h"
#include "share.h"
#include "sshare.h"
#include "socket.h"
#include "socketgz.h"
#include "var.h"

extern "C" {
#include "bltVector.h"
}

Context::Context()
{
  parent_ = NULL;
  fits = NULL;
  cfits = NULL;

  mosaicCount_ = 0;
  for (int ii=0; ii<FTY_MAXAXES; ii++) {
    naxis_[ii] =0;
    slice_[ii] =1;
  }

  mosaicType = Base::NOMOSAIC;
  mosaicSystem = Coord::WCS;

  binFunction_ = FitsHist::SUM;
  binFactor_ = Vector(1,1);
  binBufferSize_ = 1024;
  binDepth_ = 1;

  doBlock_ =0;

  doSmooth_ =0;
  smoothFunction_ =GAUSSIAN;
  smoothRadius_ =3;

  contour = NULL;
}

Context::~Context()
{
  if (contour)
    delete contour;
}

Context::Context(const Context& a)
{
  mosaicCount_ = a.mosaicCount_;
  for (int ii=0; ii<FTY_MAXAXES; ii++) {
    naxis_[ii] = a.naxis_[ii];
    slice_[ii] = a.slice_[ii];
  }

  mosaicType = a.mosaicType;
  mosaicSystem = a.mosaicSystem;

  contour = a.contour;
  auxcontours = a.auxcontours;

  frScale = a.frScale;
}

Context& Context::operator=(const Context& a)
{
  mosaicCount_ = a.mosaicCount_;
  for (int ii=0; ii<FTY_MAXAXES; ii++) {
    naxis_[ii] = a.naxis_[ii];
    slice_[ii] = a.slice_[ii];
  }

  mosaicType = a.mosaicType;
  mosaicSystem = a.mosaicSystem;

  if (contour)
    delete contour;
  contour = a.contour;
  auxcontours = a.auxcontours;

  frScale = a.frScale;
}

void Context::bltHist(char* xname, char* yname, int num)
{
  if (!fits)
    return;

  frScale.histogram(fits,num);
  double* x = frScale.histogramX();
  double* y = frScale.histogramY();

  Blt_Vector* xx;
  Blt_GetVector(parent_->interp, xname, &xx);
  Blt_ResetVector(xx, x, num, num*sizeof(double), TCL_STATIC);

  Blt_Vector* yy;
  Blt_GetVector(parent_->interp, yname, &yy);
  Blt_ResetVector(yy, y, num, num*sizeof(double), TCL_STATIC);
}

int Context::calcSlice()
{
  int cnt =1;
  for (int jj=3; jj<FTY_MAXAXES; jj++) {
    int cc =1;
    for (int ii=2; ii<jj; ii++)
      cc *= naxis_[ii];
    cnt += (slice_[jj]-1) * cc;
  }

  return cnt;
}

void Context::clearContour()
{
  if (contour)
    delete contour;
  contour = NULL;
}

int Context::fitsCount()
{
  int cnt =1;
  for (int ii=2; ii<FTY_MAXAXES; ii++)
    if (naxis_[ii])
      cnt *= naxis_[ii];
  return mosaicCount_ * cnt;
}

Vector Context::getClip()
{
  return Vector(frScale.low(), frScale.high());
}

Vector Context::getClip(FrScale::ClipMode cm, float ac)
{
  FrScale cl = frScale;
  cl.setClipMode(cm);
  cl.setAutoCutPer(ac);
  updateClip(&cl);

  // now reset
  updateClip(&frScale);

  return Vector(cl.low(),cl.high());
}

int Context::load(Base* parent, Base::MemType which, 
		  const char* fn, FitsImage* img, Base::LayerType ll)
{
  if (!img || !img->isValid()) {
    if (img)
      delete img;
    
    switch (ll) {
    case Base::IMG:
      unload();
      break;
    case Base::MASK:
      break;
    }

    return 0;
  }

  switch (ll) {
  case Base::IMG:
    fits = img;
    loadInit(Base::NOMOSAIC,Coord::WCS);
    for (int ii=0; ii<FTY_MAXAXES; ii++)
      naxis_[ii] = img->naxis(ii);
    break;

  case Base::MASK:
    mask.append(new FitsMask(parent, img, parent->maskColorName, parent->maskMark));
    break;
  }

  if (img->isHist())
    which = Base::HIST;
  else if (img->isPost())
    which = Base::POST;

  FitsImage* ptr = img;
  for (int ii=1; ii<img->nhdu(); ii++) {
    FitsImage* next = NULL;
    switch (which) {
    case Base::ALLOC:
      next = new FitsImageFitsNextAlloc(parent, fn, ptr->fitsFile(), ii+1);
      break;
    case Base::ALLOCGZ:
      next = new FitsImageFitsNextAllocGZ(parent, fn, ptr->fitsFile(), ii+1);
      break;
    case Base::CHANNEL:
      next = new FitsImageFitsNextChannel(parent, fn, ptr->fitsFile(), ii+1);
      break;
    case Base::MMAP:
      next = new FitsImageFitsNextMMap(parent, fn, ptr->fitsFile(), ii+1);
      break;
    case Base::SMMAP:
      next = new FitsImageFitsNextSMMap(parent, fn, ptr->fitsFile(), ii+1);
      break;
    case Base::MMAPINCR:
      next = new FitsImageFitsNextMMapIncr(parent, fn, ptr->fitsFile(), ii+1);
      break;
    case Base::SHARE:
      next = new FitsImageFitsNextShare(parent, fn, ptr->fitsFile(), ii+1);
      break;
    case Base::SSHARE:
      next = new FitsImageFitsNextSShare(parent, fn, ptr->fitsFile(), ii+1);
      break;
    case Base::SOCKET:
      next = new FitsImageFitsNextSocket(parent, fn, ptr->fitsFile(), ii+1);
      break;
    case Base::SOCKETGZ:
      next = new FitsImageFitsNextSocketGZ(parent, fn, ptr->fitsFile(), ii+1);
      break;
    case Base::VAR:
      next = new FitsImageFitsNextVar(parent, fn, ptr->fitsFile(), ii+1);
      break;
    case Base::HIST:
      next = new FitsImageFitsNextHist(parent, img, ptr->baseFile(), ii+1);
      break;
    case Base::POST:
      next = new FitsImageFitsNextPost(parent, img, ptr->baseFile(), ii+1);
      break;
    case Base::PHOTO:
      next = new FitsImagePhotoCubeNext(parent, fn, ptr->baseFile(), ii+1);
      break;
    }

    if (next && next->isValid()) {
      ptr->setNextSlice(next);
      ptr = next;
    }
    else {
      if (next)
	delete next;
      break;
    }
  }

  // check to see if img is really a binn'd data cube
  if (img->isHist() && ll == Base::IMG)
    updateBinFileNames();

  // finish up
  img->close();

  switch (ll) {
  case Base::IMG:
    loadFinish();
    break;
  case Base::MASK:
    loadFinishMask();
    break;
  }

  return 1;
}

int Context::loadExtCube(Base* parent, Base::MemType which, 
			 const char* fn, FitsImage* img)
{
  if (!img || !img->isValid()) {
    if (img)
      delete img;
    
    unload();
    return 0;
  }

  fits = img;
  loadInit(Base::NOMOSAIC,Coord::WCS);
  for (int ii=0; ii<2; ii++)
    naxis_[ii] = img->naxis(ii);
  naxis_[2] =1;

  // get the rest
  FitsImage* ptr = img;
  while (1) {
    FitsImage* next = NULL;
    switch (which) {
    case Base::ALLOC:
      next = new FitsImageMosaicNextAlloc(parent, fn, ptr->fitsFile(), 
					  FitsFile::NOFLUSH ,1);
      break;
    case Base::ALLOCGZ:
      next = new FitsImageMosaicNextAllocGZ(parent,fn,ptr->fitsFile(), 
					    FitsFile::NOFLUSH ,1);
      break;
    case Base::CHANNEL:
      next = new FitsImageMosaicNextChannel(parent,fn,ptr->fitsFile(), 
					    FitsFile::NOFLUSH, 1);
      break;
    case Base::MMAP:
      next = new FitsImageMosaicNextMMap(parent, fn, ptr->fitsFile(), 1);
      break;
    case Base::MMAPINCR:
      next = new FitsImageMosaicNextMMapIncr(parent, fn, ptr->fitsFile(), 1);
      break;
    case Base::SHARE:
      next = new FitsImageMosaicNextShare(parent, fn, ptr->fitsFile(), 1);
      break;
    case Base::SOCKET:
      next = new FitsImageMosaicNextSocket(parent, fn,ptr->fitsFile(), 
					   FitsFile::FLUSH,1 );
      break;
    case Base::SOCKETGZ:
      next =new FitsImageMosaicNextSocketGZ(parent,fn,ptr->fitsFile(), 
					    FitsFile::FLUSH,1 );
      break;
    case Base::VAR:
      next = new FitsImageMosaicNextVar(parent, fn, ptr->fitsFile(), 1);
      break;
    }

    if (next && next->isValid() && (next->isImage() || next->isPost())) {
      ptr->setNextSlice(next);
      ptr = next;
      naxis_[2]++;
    }
    else {
      if (next)
	delete next;
      break;
    }
  }

  // fix z params
  double ff=1-.5;
  double tt=naxis_[2]+.5;
  FitsImage* sptr = fits;
  while (sptr) {
    sptr->iparams.zmin =ff;
    sptr->iparams.zmax =tt;
    sptr->dparams.zmin =ff;
    sptr->dparams.zmax =tt;
    sptr->cparams.zmin =ff;
    sptr->cparams.zmax =tt;
    sptr = sptr->nextSlice();
  }

  // finish up
  img->close();
  loadFinish();

  return 1;
}

void Context::loadInit(Base::MosaicType type, Coord::CoordSystem sys)
{
  cfits = fits;
  mosaicCount_ = 1;
  for (int ii=0; ii<FTY_MAXAXES; ii++) {
    naxis_[ii] =0;
    slice_[ii] =1;
  }
  
  mosaicType = type;
  mosaicSystem = sys;
}

int Context::loadMosaic(Base* parent, Base::MemType which, 
			const char* fn, FitsImage* img, Base::LayerType ll, 
			Base::MosaicType type, Coord::CoordSystem sys)
{
  if (!img || !img->isValid()) {
    if (img)
      delete img;
    return 0;
  }

  switch (ll) {
  case Base::IMG:
    if (fits) {
      FitsImage* ptr = fits;
      while (ptr && ptr->nextMosaic())
	ptr = ptr->nextMosaic();
      ptr->setNextMosaic(img);
      mosaicCount_++;
    }
    else {
      fits = img;
      loadInit(type,sys);
      for (int ii=0; ii<FTY_MAXAXES; ii++)
	naxis_[ii] = img->naxis(ii);
    }
    break;

  case Base::MASK:
    FitsMask* msk = mask.tail();
    if (msk) {
      FitsImage* mskimg = msk->mask();
      while (mskimg && mskimg->nextMosaic())
	mskimg = mskimg->nextMosaic();
      mskimg->setNextMosaic(img);
    }
    else
      mask.append(new FitsMask(parent, img, parent->maskColorName, parent->maskMark));
    break;
  }

  switch (mosaicType) {
  case Base::IRAF:
    if (!img->processKeywordsIRAF(fits))
      return 0;
    break;
  case Base::WCSMOSAIC:
    if (!img->processKeywordsWCS(fits, mosaicSystem))
      return 0;
    break;
  }

  if (img->isPost())
    which = Base::POST;

  // get the rest of slices
  FitsImage* sptr = img;
  for (int ii=1; ii<img->nhdu(); ii++) {
    FitsImage* next = NULL;
    switch (which) {
    case Base::ALLOC:
      next = new FitsImageFitsNextAlloc(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::ALLOCGZ:
      next = new FitsImageFitsNextAllocGZ(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::CHANNEL:
      next = new FitsImageFitsNextChannel(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::MMAP:
      next = new FitsImageFitsNextMMap(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::SMMAP:
      next = new FitsImageFitsNextSMMap(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::MMAPINCR:
      next = new FitsImageFitsNextMMapIncr(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::SHARE:
      next = new FitsImageFitsNextShare(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::SSHARE:
      next = new FitsImageFitsNextSShare(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::SOCKET:
      next = new FitsImageFitsNextSocket(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::SOCKETGZ:
      next = new FitsImageFitsNextSocketGZ(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::VAR:
      next = new FitsImageFitsNextVar(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::POST:
      next = new FitsImageFitsNextPost(parent, img, sptr->baseFile(), ii+1);
      break;
    case Base::PHOTO:
      next = new FitsImagePhotoCubeNext(parent, fn, sptr->baseFile(), ii+1);
      break;
    }

    if (next && next->isValid()) {
      sptr->setNextSlice(next);
      sptr = next;

      switch (mosaicType) {
      case Base::IRAF:
	if (!next->processKeywordsIRAF(fits))
	  return 0;
	break;
      case Base::WCSMOSAIC:
	if (!next->processKeywordsWCS(fits, mosaicSystem))
	  return 0;
	break;
      }
    }
    else {
      if (next)
	delete next;
      break;
    }
  }

  // finish up
  img->close();

  switch (ll) {
  case Base::IMG:
    loadFinish();
    break;
  case Base::MASK:
    loadFinishMask();
    break;
  }

  return 1;
}

int Context::loadMosaicImage(Base* parent, Base::MemType which, 
			     const char* fn, FitsImage* img, Base::LayerType ll,
			     Base::MosaicType type, Coord::CoordSystem sys)
{
  if (!img || !img->isValid()) {
    if (img)
      delete img;

    switch (ll) {
    case Base::IMG:
      unload();
      break;
    case Base::MASK:
      break;
    }

    return 0;
  }

  switch (ll) {
  case Base::IMG:
    fits = img;
    loadInit(type,sys);
    for (int ii=0; ii<FTY_MAXAXES; ii++)
      naxis_[ii] = img->naxis(ii);
    break;

  case Base::MASK:
    mask.append(new FitsMask(parent, img, parent->maskColorName, parent->maskMark));
    break;
  }

  Base::MemType sav = which;

  switch (type) {
  case Base::IRAF:
    if (!img->processKeywordsIRAF(fits)) {
      unload();
      return 0;
    }
    break;
  case Base::WCSMOSAIC:
    if (!img->processKeywordsWCS(fits, mosaicSystem)) {
      unload();
      return 0;
    }
    break;
  }

  // get the rest of slices
  FitsImage* sptr = img;
  if (img->isPost())
    which = Base::POST;

  for (int ii=1; ii<img->nhdu(); ii++) {
    FitsImage* next = NULL;
    switch (which) {
    case Base::ALLOC:
      next = new FitsImageFitsNextAlloc(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::ALLOCGZ:
      next = new FitsImageFitsNextAllocGZ(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::CHANNEL:
      next = new FitsImageFitsNextChannel(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::MMAP:
      next = new FitsImageFitsNextMMap(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::SMMAP:
      next = new FitsImageFitsNextSMMap(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::MMAPINCR:
      next = new FitsImageFitsNextMMapIncr(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::SHARE:
      next = new FitsImageFitsNextShare(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::SSHARE:
      next = new FitsImageFitsNextSShare(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::SOCKET:
      next = new FitsImageFitsNextSocket(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::SOCKETGZ:
      next = new FitsImageFitsNextSocketGZ(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::VAR:
      next = new FitsImageFitsNextVar(parent, fn, sptr->fitsFile(), ii+1);
      break;
    case Base::POST:
      next = new FitsImageFitsNextPost(parent, img, sptr->baseFile(), ii+1);
      break;
    }

    if (next && next->isValid()) {
      sptr->setNextSlice(next);
      sptr = next;

      switch (type) {
      case Base::IRAF:
	if (!next->processKeywordsIRAF(fits)) {
	  unload();
	  return 0;
	}
	break;
      case Base::WCSMOSAIC:
	if (!next->processKeywordsWCS(fits, mosaicSystem)) {
	  unload();
	  return 0;
	}
	break;
      }
    }
    else {
      if (next)
	delete next;
      break;
    }
  }

  // get the rest of mosaic
  FitsImage* ptr = img;
  while (1) {
    // restore which
    which = sav;

    FitsImage* next = NULL;
    switch (which) {
    case Base::ALLOC:
      next = new FitsImageMosaicNextAlloc(parent, fn, ptr->fitsFile(), 
					  FitsFile::NOFLUSH, 1);
      break;
    case Base::ALLOCGZ:
      next = new FitsImageMosaicNextAllocGZ(parent,fn,ptr->fitsFile(), 
					    FitsFile::NOFLUSH, 1);
      break;
    case Base::CHANNEL:
      next = new FitsImageMosaicNextChannel(parent,fn,ptr->fitsFile(), 
					    FitsFile::NOFLUSH, 1);
      break;
    case Base::MMAP:
      next = new FitsImageMosaicNextMMap(parent, fn, ptr->fitsFile(), 1);
      break;
    case Base::MMAPINCR:
      next = new FitsImageMosaicNextMMapIncr(parent, fn, ptr->fitsFile(), 1);
      break;
    case Base::SHARE:
      next = new FitsImageMosaicNextShare(parent, fn, ptr->fitsFile(), 1);
      break;
    case Base::SOCKET:
      next = new FitsImageMosaicNextSocket(parent, fn,ptr->fitsFile(), 
					   FitsFile::FLUSH, 1);
      break;
    case Base::SOCKETGZ:
      next =new FitsImageMosaicNextSocketGZ(parent,fn,ptr->fitsFile(), 
					    FitsFile::FLUSH, 1);
      break;
    case Base::VAR:
      next = new FitsImageMosaicNextVar(parent, fn, ptr->fitsFile(), 1);
      break;
    }

    if (next && next->isValid() && (next->isImage() || next->isPost())) {
      ptr->setNextMosaic(next);
      ptr = next;

      switch (type) {
      case Base::IRAF:
	if (!next->processKeywordsIRAF(fits)) {
	  unload();
	  return 0;
	}
	break;
      case Base::WCSMOSAIC:
	if (!next->processKeywordsWCS(fits, mosaicSystem)) {
	  unload();
	  return 0;
	}
	break;
      }

      if (ll == Base::IMG)
	mosaicCount_++;

      if (img->isPost())
	which = Base::POST;

      // get rest of slices
      for (int ii=1; ii<img->nhdu(); ii++) {
	FitsImage* snext = NULL;
	switch (which) {
	case Base::ALLOC:
	  snext = new FitsImageFitsNextAlloc(parent, fn, next->fitsFile(), ii+1);
	  break;
	case Base::ALLOCGZ:
	  snext = new FitsImageFitsNextAllocGZ(parent, fn, next->fitsFile(),ii+1);
	  break;
	case Base::CHANNEL:
	  snext = new FitsImageFitsNextChannel(parent, fn, next->fitsFile(),ii+1);
	  break;
	case Base::MMAP:
	  snext = new FitsImageFitsNextMMap(parent, fn, next->fitsFile(), ii+1);
	  break;
	case Base::SMMAP:
	  snext = new FitsImageFitsNextSMMap(parent, fn, next->fitsFile(), ii+1);
	  break;
	case Base::MMAPINCR:
	  snext = new FitsImageFitsNextMMapIncr(parent,fn,next->fitsFile(), ii+1);
	  break;
	case Base::SHARE:
	  snext = new FitsImageFitsNextShare(parent, fn, next->fitsFile(), ii+1);
	  break;
	case Base::SSHARE:
	  snext = new FitsImageFitsNextSShare(parent, fn, next->fitsFile(), ii+1);
	  break;
	case Base::SOCKET:
	  snext = new FitsImageFitsNextSocket(parent, fn, next->fitsFile(), ii+1);
	  break;
	case Base::SOCKETGZ:
	  snext = new FitsImageFitsNextSocketGZ(parent,fn,next->fitsFile(), ii+1);
	  break;
	case Base::VAR:
	  snext = new FitsImageFitsNextVar(parent, fn, next->fitsFile(), ii+1);
	  break;
	case Base::POST:
	  snext = new FitsImageFitsNextPost(parent, ptr, next->baseFile(), ii+1);
	  break;
	}

	if (snext && snext->isValid()) {
	  next->setNextSlice(snext);
	  next = snext;

	  switch (type) {
	  case Base::IRAF:
	    if (!snext->processKeywordsIRAF(fits)) {
	      unload();
	      return 0;
	    }
	    break;
	  case Base::WCSMOSAIC:
	    if (!snext->processKeywordsWCS(fits, mosaicSystem)) {
	      unload();
	      return 0;
	    }
	    break;
	  }
	}
	else {
	  if (snext)
	    delete snext;
	  break;
	}
      }
    }
    else {
      if (next)
	delete next;
      break;
    }
  }
  
  // finish up
  img->close();

  switch (ll) {
  case Base::IMG:
    loadFinish();
    break;
  case Base::MASK:
    loadFinishMask();
    break;
  }

  return 1;
}

int Context::loadMosaicWFPC2(Base* parent, Base::MemType which,
			     const char* fn, FitsImage* img)
{
  if (!img || !img->isValid()) {
    if (img)
      delete img;

    unload();
    return 0;
  }

  // Its legal, save it
  fits = img;
  loadInit(Base::WFPC2,Coord::WCS);

  // remember in case of compress
  Base::MemType sav = which;

  if (img->isPost())
    which = Base::POST;

  // get the rest
  {
    FitsImage* ptr = fits;
    for (int i=1; i<4; i++) {
      FitsImage* next = NULL;
      switch (which) {
      case Base::ALLOC:
	next = new FitsImageFitsNextAlloc(parent, fn, ptr->fitsFile(), 1);
	break;
      case Base::ALLOCGZ:
	next = new FitsImageFitsNextAllocGZ(parent, fn, ptr->fitsFile(), 1);
	break;
      case Base::CHANNEL:
	next = new FitsImageFitsNextChannel(parent, fn, ptr->fitsFile(), 1);
	break;
      case Base::MMAP:
	next = new FitsImageFitsNextMMap(parent, fn, ptr->fitsFile(), 1);
	break;
      case Base::MMAPINCR:
	next = new FitsImageFitsNextMMapIncr(parent, fn, ptr->fitsFile(), 1);
	break;
      case Base::SHARE:
	next = new FitsImageFitsNextShare(parent, fn, ptr->fitsFile(), 1);
	break;
      case Base::SOCKET:
	next = new FitsImageFitsNextSocket(parent, fn, ptr->fitsFile(), 1);
	break;
      case Base::SOCKETGZ:
	next = new FitsImageFitsNextSocketGZ(parent, fn, ptr->fitsFile(), 1);
	break;
      case Base::VAR:
	next = new FitsImageFitsNextVar(parent, fn, ptr->fitsFile(), 1);
	break;
      case Base::POST:
	next = new FitsImageFitsNextPost(parent, img, ptr->baseFile(), 1);
	break;
      }

      if (next && next->isValid()) {
	ptr->setNextMosaic(next);
	ptr = next;
	mosaicCount_++;
      }
      else {
	if (next)
	  delete next;
	break;
      }
    }
  }

  // restore which
  which = sav;

  // ok, do we have 4 images?
  if (mosaicCount_ != 4) {
    unload();
    return 0;
  }

  // now, find WCS table
  FitsFile* table;
  switch (which) {
  case Base::ALLOC:
    table = new FitsMosaicNextAlloc(fits->fitsFile(), FitsFile::NOFLUSH);
    break;
  case Base::ALLOCGZ:
    table = new FitsMosaicNextAllocGZ(fits->fitsFile(), FitsFile::NOFLUSH);
    break;
  case Base::CHANNEL:
    table = new FitsMosaicNextChannel(fits->fitsFile(), FitsFile::NOFLUSH);
    break;
  case Base::MMAP:
    table = new FitsMosaicNextMMap(fits->fitsFile());
    break;
  case Base::MMAPINCR:
    table = new FitsMosaicNextMMapIncr(fits->fitsFile());
    break;
  case Base::SHARE:
    table = new FitsMosaicNextShare(fits->fitsFile());
    break;
  case Base::SOCKET:
    table = new FitsMosaicNextSocket(fits->fitsFile(), FitsFile::FLUSH);
    break;
  case Base::SOCKETGZ:
    table = new FitsMosaicNextSocketGZ(fits->fitsFile(), FitsFile::FLUSH);
    break;
  case Base::VAR:
    table = new FitsMosaicNextVar(fits->fitsFile());
    break;
  }

  if (!table || !table->isValid() || !table->isAsciiTable()) {
    if (table)
      delete table;

    unload();
    return 0;
  }

  // read WCS from table
  {
    FitsHead* th = table->head();
    if (th->naxes() != 2) {
      if (table)
	delete table;

      unload();
      return 0;
    }

    FitsTableHDU* thdu = (FitsTableHDU*)th->hdu();

    FitsColumn* crval1 = thdu->find("CRVAL1");
    FitsColumn* crval2 = thdu->find("CRVAL2");
    FitsColumn* crpix1 = thdu->find("CRPIX1");
    FitsColumn* crpix2 = thdu->find("CRPIX2");

    FitsColumn* cd1_1 = thdu->find("CD1_1");
    FitsColumn* cd1_2 = thdu->find("CD1_2");
    FitsColumn* cd2_1 = thdu->find("CD2_1");
    FitsColumn* cd2_2 = thdu->find("CD2_2");

    FitsColumn* ctype1 = thdu->find("CTYPE1");
    FitsColumn* ctype2 = thdu->find("CTYPE2");

    char* tptr = (char*)table->data();
    int rows = thdu->rows();
    int rowlen = thdu->width();

    if (rows != 4) {
      unload();
      return 0;
    }

    FitsImage* ptr = fits;

    // reset count for processKeyWords()
    mosaicCount_ =0;

    for (int i=0; i<rows; i++, tptr+=rowlen) {
      istringstream istr(ios::in|ios::out);
      ostream ostr(istr.rdbuf());
      ostr << "CRVAL1 = " << crval1->str(tptr) << endl
	   << "CRVAL2 = " << crval2->str(tptr) << endl
	   << "CRPIX1 = " << crpix1->str(tptr) << endl
	   << "CRPIX2 = " << crpix2->str(tptr) << endl
	   << "CD1_1  = " <<  cd1_1->str(tptr) << endl
	   << "CD1_2  = " <<  cd1_2->str(tptr) << endl
	   << "CD2_1  = " <<  cd2_1->str(tptr) << endl
	   << "CD2_2  = " <<  cd2_2->str(tptr) << endl
	   << "CTYPE1 = " << '\'' << ctype1->str(tptr) << '\'' << endl
	   << "CTYPE2 = " << '\'' << ctype2->str(tptr) << '\'' << endl
	   << ends;
      FitsHead* r = parent->parseWCS(istr);

      // fix fitsimage params
      ptr->replaceWCS(r);
      ptr->processKeywordsWCS(fits, Coord::WCS);

      delete r;
      ptr = ptr->nextMosaic();
      mosaicCount_++;
    }
  }

  if (table)
    delete table;

  // finish up
  img->close();

  loadFinish();
  return 1;
}

void Context::loadFinish()
{
  FitsImage* ptr = fits;
  while (ptr && ptr->nextMosaic()) {
    int jj=0;
    FitsImage* sptr = ptr;
    while (sptr) {
      if (sptr->nextMosaic() == NULL) {
	// ok, let's figure out next inline
	FitsImage* mptr = ptr->nextMosaic();
	for (int nn=0; nn<jj; nn++)
	  mptr = mptr->nextSlice();
	sptr->setNextMosaic(mptr);
      }
      jj++;
      sptr = sptr->nextSlice();
    }
    ptr = ptr->nextMosaic();
  }

  updateClip(&frScale);
  frScale.clearHistequ();
  frScale.clearHistogram();
}

void Context::loadFinishMask()
{
  // only do last loaded Mask
  FitsMask* msk = mask.tail();
  if (msk) {
    FitsImage* ptr = msk->mask();
    while (ptr && ptr->nextMosaic()) {
      int jj=0;
      FitsImage* sptr = ptr;
      while (sptr) {
	if (sptr->nextMosaic() == NULL) {
	  // ok, let's figure out next inline
	  FitsImage* mptr = ptr->nextMosaic();
	  for (int nn=0; nn<jj; nn++)
	    mptr = mptr->nextSlice();
	  sptr->setNextMosaic(mptr);
	}
	jj++;
	sptr = sptr->nextSlice();
      }
      ptr = ptr->nextMosaic();
    }
  }
}

int Context::loadSlice(Base* parent, Base::MemType which, const char* fn,
		       FitsImage* img)
{
  if (!img || !img->isValid()) {
    if (img)
      delete img;
    return 0;
  }

  if (fits) {
    FitsImage* ptr = fits;
    while (ptr && ptr->nextSlice())
      ptr = ptr->nextSlice();
    ptr->setNextSlice(img);
    naxis_[2]++;
  }
  else {
    fits = img;
    loadInit(Base::NOMOSAIC,Coord::WCS);
    for (int ii=0; ii<2; ii++)
      naxis_[ii] = img->naxis(ii);
    naxis_[2] =1;
  }

  // fix z params
  double ff=1-.5;
  double tt=naxis_[2]+.5;
  FitsImage* sptr = fits;
  while (sptr) {
    sptr->iparams.zmin =ff;
    sptr->iparams.zmax =tt;
    sptr->dparams.zmin =ff;
    sptr->dparams.zmax =tt;
    sptr->cparams.zmin =ff;
    sptr->cparams.zmax =tt;
    sptr = sptr->nextSlice();
  }

  // finish up
  img->close();
  loadFinish();

  return 1;
}

int Context::naxes()
{
  for (int ii=FTY_MAXAXES-1; ii>=2; ii--) {
    if (naxis_[ii])
      return ii;
  }
  return 2;
}

int Context::nhdu()
{
  int dd =1;
  for (int ii=2; ii<FTY_MAXAXES; ii++)
    if (naxis_[ii])
      dd *= naxis_[ii];
  return dd;
}

void Context::render(FitsImage::RenderStage stage)
{
  FitsImage* ptr = fits;
  while (ptr) {
    FitsImage* sptr = ptr;
    while (sptr) {
      sptr->render(stage);
      sptr = sptr->nextSlice();
    }
    ptr = ptr->nextMosaic();
  }

  updateClip(&frScale);
  frScale.clearHistequ();
  frScale.clearHistogram();
}

void Context::setBinFactor(const Vector& b) {
  Vector bb = b;
  binFactor_[0] *= bb[0] <= 0 ? 1 : bb[0];
  binFactor_[1] *= bb[1] <= 0 ? 1 : bb[1];
}

void Context::setBinToFactor(const Vector& b) {
  Vector bb = b;
  binFactor_[0] = bb[0] <= 0 ? 1 : bb[0];
  binFactor_[1] = bb[1] <= 0 ? 1 : bb[1];
}

void Context::setContour(FVContour* cc)
{
  if (contour)
    delete contour;
  contour = cc;
}

void Context::unload()
{
  FitsImage* ptr = fits;
  while (ptr) {
    FitsImage* sptr = ptr->nextSlice();
    while (sptr) {
      FitsImage* stmp = sptr->nextSlice();
      delete sptr;
      sptr = stmp;
    }

    FitsImage* tmp = ptr->nextMosaic();
    delete ptr;
    ptr = tmp;
  }

  fits = NULL;
  cfits = NULL;

  mask.deleteAll();

  mosaicCount_ = 0;
  for (int ii=0; ii<FTY_MAXAXES; ii++) {
    naxis_[ii] =0;
    slice_[ii] =1;
  }

  mosaicType = Base::NOMOSAIC;
  mosaicSystem = Coord::WCS;

  if (contour)
    delete contour;
  contour = NULL;

  auxcontours.deleteAll();

  frScale.resetScanMode();
  updateClip(&frScale);
  frScale.clearHistequ();
  frScale.clearHistogram();
}

void Context::updateBin(Base* parent)
{
  if (!fits->isHist())
    return;

  if (DebugPerf)
    cerr << "updateBin " << endl;

  // delete any previous slices
  {
    FitsImage* ptr = fits->nextSlice();
    fits->setNextSlice(NULL);
    while (ptr) {
      FitsImage* tmp = ptr->nextSlice();
      delete ptr;
      ptr = tmp;
    }
  }

  // finish bin
  loadInit(Base::NOMOSAIC,Coord::WCS);
  for (int ii=0; ii<2; ii++)
    naxis_[ii] =fits->naxis(ii);

  // bin data cube
  int bd = binDepth_;
  if (bd > 1) {
    naxis_[2] =1; // first
    FitsImage* ptr = fits;
    for (int ii=1; ii<bd; ii++) {
      FitsImage* next = NULL;
      next = new FitsImageFitsNextHist(parent, fits, ptr->baseFile(), 1);
      if (next && next->isValid()) {
	ptr->setNextSlice(next);
	ptr = next;
	naxis_[2]++;
      }
      else {
	if (next)
	  delete next;
	break;
      }
    }
  }

  updateBinFileNames();
  updateClip(&frScale);
  frScale.clearHistequ();
  frScale.clearHistogram();
}

void Context::updateBinFileNames()
{
  if (!fits->isHist())
    return;

  char* zcol = (char*)fits->getHistZ();
  int bd = binDepth_;
  if (bd>1 && zcol) {
    Vector zlim = fits->getHistColMinMax(zcol);
    double zlen = zlim[1]-zlim[0];
    double zdelta = zlen/bd;

    double zptr = zlim[0];
    FitsImage* ptr = fits;
    for (int ii=0; ii<fits->depth();ii++,ptr=ptr->nextSlice(),zptr+=zdelta) {
      ostringstream str;
      str << zcol << ">=" << zptr << '&' << zcol << '<' << zptr+zdelta << ends;
      ptr->setBinSliceFilter(str.str().c_str());
      ptr->updateFileName();
    } 
  }
  else {
    fits->setBinSliceFilter(NULL);
    fits->updateFileName();
  }
}

void Context::updateClip()
{
  updateClip(&frScale);
  frScale.clearHistequ();
}

void Context::updateClip(FrScale* fr)
{
  if (DebugPerf)
    cerr << "updateClip " << endl;

  // no fits
  if (!fits) {
    if (fr->clipMode() != FrScale::USERCLIP) {
      fr->setLow(DEFAULTLOW);
      fr->setHigh(DEFAULTHIGH);
    }
    else {
      fr->setLow(fr->uLow());
      fr->setHigh(fr->uHigh());
    }

    return;
  }

  // find min/max
  fr->setMin(DBL_MAX);
  fr->setMax(-DBL_MAX);
  fr->setLow(DBL_MAX);
  fr->setHigh(-DBL_MAX);

  FitsImage* ptr = fits;
  while (ptr) {
    FitsImage* sptr = ptr;
    while (sptr) {
      sptr->updateClip(fr);

      // find over-all min/max
      if (fr->min() > sptr->getMinDouble())
	fr->setMin(sptr->getMinDouble());
      
      if (fr->max() <= sptr->getMaxDouble())
	fr->setMax(sptr->getMaxDouble());

      // find low/high
      if (fr->low() > sptr->getLowDouble())
	fr->setLow(sptr->getLowDouble());

      if (fr->high() <= sptr->getHighDouble())
	fr->setHigh(sptr->getHighDouble());

      sptr = sptr->nextSlice();
    }

    ptr = ptr->nextMosaic();
  }

  // set global
  if (fr->clipScope()==FrScale::GLOBAL) {
    FitsImage* ptr = fits;
    while (ptr) {
      FitsImage* sptr = ptr;
      while (sptr) {
	sptr->setClip(fr->low(), fr->high());
	sptr = sptr->nextSlice();
      }
      ptr = ptr->nextMosaic();
    }
  }
}

void Context::updateContours()
{
  // update any contours
  if (contour)
    contour->update(cfits);
}

void Context::updateContours(const Matrix& mx)
{
  if (contour)
    contour->updateCoords(mx);

  Contour* ptr=auxcontours.head();
  while (ptr) {
    ptr->updateCoords(mx);
    ptr=ptr->next();
  }
}

void Context::updateSlice(int id, int ss)
{
  // updateSlice() ranges 1-n
  if (!fits)
    return;

  // check bounds
  if (ss<1)
    ss = 1;
  else if (ss>naxis_[id])
    ss = naxis_[id];

  slice_[id] = ss;

  int cnt =slice_[2];
  for (int jj=3; jj<FTY_MAXAXES; jj++) {
    int cc =1;
    for (int ii=2; ii<jj; ii++)
      cc *= naxis_[ii];
    cnt += (slice_[jj]-1) * cc;
  }

  cfits = fits;
  for (int ii=1; ii<cnt; ii++)
    if (cfits)
      cfits = cfits->nextSlice();

  updateContours();
}

