// Copyright (C) 1999-2015
// 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"

#include "head.h"

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

Context::Context()
{
  parent_ = NULL;

  bfits_ =NULL;
  fits =NULL;
  cfits =NULL;

  manageAxes_ =0;
  axesOrder_ =123;

  loadInit(0, Base::NOMOSAIC, Coord::WCS);

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

  blockFactor_ = Vector(1,1);

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

  contour =NULL;

  thread_ =NULL;
  targ_ =NULL;
}

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

void Context::analysis()
{
  if (targ_)
    delete [] targ_;
  targ_ = new t_smooth_arg[parent_->nthreads_];

  if (thread_)
    delete [] thread_;
  thread_ = new pthread_t[parent_->nthreads_];

  int cnt =0;
  FitsImage* ptr = fits;
  while (ptr) {
    FitsImage* sptr = ptr;
    while (sptr) {
      sptr->analysis(doSmooth_, &thread_[cnt], &targ_[cnt]);
      cnt++;
      if (cnt == parent_->nthreads_) {
	if (doSmooth_) {
	  for (int ii=0; ii<cnt; ii++) {
	    int rr = pthread_join(thread_[ii], NULL);
	    if (rr)
	      internalError("Unable to Join Thread");

	    if (targ_[ii].kernel)
	      delete [] targ_[ii].kernel;
	    if (targ_[ii].src)
	      delete [] targ_[ii].src;
	  }
	}
	cnt =0;
      }
      sptr = sptr->nextSlice();
    }
    ptr = ptr->nextMosaic();
  }

  if (doSmooth_) {
    for (int ii=0; ii<cnt; ii++) {
      int rr = pthread_join(thread_[ii], NULL);
      if (rr)
	internalError("Unable to Join Thread");

      if (targ_[ii].kernel)
	delete [] targ_[ii].kernel;
      if (targ_[ii].src)
	delete [] targ_[ii].src;
    }
  }

  if (targ_)
    delete [] targ_;
  targ_ =NULL;

  if (thread_)
    delete [] thread_;
  thread_ =NULL;

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

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

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

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

  Blt_Vector* yy;
  Blt_GetVector(parent_->interp, yname, &yy);
  Blt_ResetVector(yy, y, nn, nn*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;
}

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

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

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:
    bfits_ = img;
    loadInit(1, Base::NOMOSAIC,Coord::WCS);
    for (int ii=2; ii<FTY_MAXAXES; ii++) {
      int nn = img->naxis(ii);
      baxis_[ii] = nn ? nn : 1;
    }
    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->isValid()) {
      ptr->setNextSlice(next);
      ptr = next;
    }
    else {
      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:
    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;
  }

  bfits_ = img;
  loadInit(1, Base::NOMOSAIC,Coord::WCS);

  // get the rest
  FitsImage* ptr = img;
  FitsImage* mptr = ptr;
  FitsImage* tmp =NULL;
  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 previous was a table, delete now, we're done with it
    if (tmp) {
      delete tmp;
      tmp =NULL;
    }

    if (next->isImage() || next->isTable()) {
      if (next->isValid()) {
	mptr->setNextSlice(next);
	ptr = next;
	mptr = ptr;
	baxis_[2]++;
      }
      else {
	// must be a table, just ignore, and delete next time thru
	ptr = next;
	tmp = next;
      }
    }
    else {
      delete next;
      break;
    }
  }

  // fix z params (in data coords)
  double ff=0;
  double tt=baxis_[2];
  FitsImage* sptr = bfits_;
  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(int cnt, Base::MosaicType type, Coord::CoordSystem sys)
{
  mosaicCount_ = cnt;
  mosaicType = type;
  mosaicSystem = sys;
  for (int ii=0; ii<FTY_MAXAXES; ii++) {
    baxis_[ii] =1;
    slice_[ii] =1;
  }
  naxis_ = baxis_;
}

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 (bfits_) {
      FitsImage* ptr = bfits_;
      while (ptr && ptr->nextMosaic())
	ptr = ptr->nextMosaic();
      ptr->setNextMosaic(img);
      mosaicCount_++;
    }
    else {
      bfits_ = img;
      loadInit(1, type,sys);
      for (int ii=2; ii<FTY_MAXAXES; ii++) {
	int nn = img->naxis(ii);
	baxis_[ii] = nn ? nn : 1;
      }
    }
    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(bfits_))
      return 0;
    break;
  case Base::WCSMOSAIC:
    if (!img->processKeywordsWCS(bfits_, 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->isValid()) {
      sptr->setNextSlice(next);
      sptr = next;

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

  // finish up
  img->close();

  switch (ll) {
  case Base::IMG:
    loadFinishMosaic(fits);
    loadFinish();
    break;
  case Base::MASK:
    // only do last loaded Mask
    FitsMask* msk = mask.tail();
    if (msk)
      loadFinishMosaic(msk->mask());
    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:
    bfits_ = img;
    loadInit(1, type,sys);
    for (int ii=2; ii<FTY_MAXAXES; ii++) {
      int nn = img->naxis(ii);
      baxis_[ii] = nn ? nn : 1;
    }
    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(bfits_)) {
      unload();
      return 0;
    }
    break;
  case Base::WCSMOSAIC:
    if (!img->processKeywordsWCS(bfits_, 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->isValid()) {
      sptr->setNextSlice(next);
      sptr = next;

      switch (type) {
      case Base::IRAF:
	if (!next->processKeywordsIRAF(bfits_)) {
	  unload();
	  return 0;
	}
	break;
      case Base::WCSMOSAIC:
	if (!next->processKeywordsWCS(bfits_, mosaicSystem)) {
	  unload();
	  return 0;
	}
	break;
      }
    }
    else {
      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->isValid()) {
      ptr->setNextMosaic(next);
      ptr = next;

      switch (type) {
      case Base::IRAF:
	if (!next->processKeywordsIRAF(bfits_)) {
	  unload();
	  return 0;
	}
	break;
      case Base::WCSMOSAIC:
	if (!next->processKeywordsWCS(bfits_, 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->isValid()) {
	  next->setNextSlice(snext);
	  next = snext;

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

  switch (ll) {
  case Base::IMG:
    loadFinishMosaic(bfits_);
    loadFinish();
    break;
  case Base::MASK:
    FitsMask* msk = mask.tail();
    if (msk)
      loadFinishMosaic(msk->mask());
    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
  bfits_ = img;
  loadInit(1, Base::WFPC2,Coord::WCS);

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

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

  // get the rest
  {
    FitsImage* ptr = img;
    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->isValid()) {
	ptr->setNextMosaic(next);
	ptr = next;
	mosaicCount_++;
      }
      else {
	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(img->fitsFile(), FitsFile::NOFLUSH);
    break;
  case Base::ALLOCGZ:
    table = new FitsMosaicNextAllocGZ(img->fitsFile(), FitsFile::NOFLUSH);
    break;
  case Base::CHANNEL:
    table = new FitsMosaicNextChannel(img->fitsFile(), FitsFile::NOFLUSH);
    break;
  case Base::MMAP:
    table = new FitsMosaicNextMMap(img->fitsFile());
    break;
  case Base::MMAPINCR:
    table = new FitsMosaicNextMMapIncr(img->fitsFile());
    break;
  case Base::SHARE:
    table = new FitsMosaicNextShare(img->fitsFile());
    break;
  case Base::SOCKET:
    table = new FitsMosaicNextSocket(img->fitsFile(), FitsFile::FLUSH);
    break;
  case Base::SOCKETGZ:
    table = new FitsMosaicNextSocketGZ(img->fitsFile(), FitsFile::FLUSH);
    break;
  case Base::VAR:
    table = new FitsMosaicNextVar(img->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 = bfits_;

    // 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 = bfits_->parseWCS(istr);

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

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

  if (table)
    delete table;

  // finish up
  img->close();

  loadFinish();
  return 1;
}

void Context::loadFinish()
{
  // just in case of 3d
  parent_->cancelDetach();

  if (manageAxes_) {
    delete [] naxis_;
    deleteFits(fits);
    manageAxes_ =0;
  }
  fits = bfits_;
  naxis_ = baxis_;

  if (axesOrder_ != 123)
    reorderAxes();
  
  for (int ii=0; ii<FTY_MAXAXES; ii++)
    slice_[ii] =1;
  cfits = fits;

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

void Context::loadFinishMosaic(FitsImage* ptr)
{
  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 (bfits_) {
    FitsImage* ptr = bfits_;
    while (ptr && ptr->nextSlice())
      ptr = ptr->nextSlice();
    ptr->setNextSlice(img);
    baxis_[2]++;
  }
  else {
    bfits_ = img;
    loadInit(1, Base::NOMOSAIC,Coord::WCS);
  }

  // fix z params (in data coords)
  double ff=0;
  double tt=baxis_[2];
  FitsImage* sptr = bfits_;
  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]>1)
      return ii+1;
  }
  return 2;
}

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

void* reorder132(void* tt)
{
  t_reorder_arg* targ = (t_reorder_arg*)tt;
  char* dest = targ->dest;
  char** sjv = targ->sjv;
  int ww = targ->ww;
  int hh = targ->hh;
  int dd = targ->dd;
  int bz = targ->bz;
  int jj = targ->mm;

  for (int kk=0; kk<dd; kk++) {
    memcpy(dest, sjv[kk]+(jj*ww)*bz, ww*bz);
    dest += ww*bz;
    /*
      for (int ii=0; ii<ww; ii++) {
      memcpy(dest, sjv[kk]+(jj*ww+ii)*bz, bz);
      dest += bz;
      }
    */
  }
}

void* reorder213(void* tt)
{
  t_reorder_arg* targ = (t_reorder_arg*)tt;
  char* dest = targ->dest;
  char** sjv = targ->sjv;
  int ww = targ->ww;
  int hh = targ->hh;
  int dd = targ->dd;
  int bz = targ->bz;
  int kk = targ->mm;

  for (int ii=0; ii<ww; ii++) {
    for (int jj=0; jj<hh; jj++) {
      memcpy(dest, sjv[kk]+(jj*ww+ii)*bz, bz);
      dest += bz;
    }
  }
}

void* reorder231(void* tt)
{
  t_reorder_arg* targ = (t_reorder_arg*)tt;
  char* dest = targ->dest;
  char** sjv = targ->sjv;
  int ww = targ->ww;
  int hh = targ->hh;
  int dd = targ->dd;
  int bz = targ->bz;
  int ii = targ->mm;

  for (int kk=0; kk<dd; kk++) {
    for (int jj=0; jj<hh; jj++) {
      memcpy(dest, sjv[kk]+(jj*ww+ii)*bz, bz);
      dest += bz;
    }
  }
}

void* reorder312(void* tt)
{
  t_reorder_arg* targ = (t_reorder_arg*)tt;
  char* dest = targ->dest;
  char** sjv = targ->sjv;
  int ww = targ->ww;
  int hh = targ->hh;
  int dd = targ->dd;
  int bz = targ->bz;
  int jj = targ->mm;

  for (int ii=0; ii<ww; ii++) {
    for (int kk=0; kk<dd; kk++) {
      memcpy(dest, sjv[kk]+(jj*ww+ii)*bz, bz);
      dest += bz;
    }
  }
}

void* reorder321(void* tt)
{
  t_reorder_arg* targ = (t_reorder_arg*)tt;
  char* dest = targ->dest;
  char** sjv = targ->sjv;
  int ww = targ->ww;
  int hh = targ->hh;
  int dd = targ->dd;
  int bz = targ->bz;
  int ii = targ->mm;

  for (int jj=0; jj<hh; jj++) {
    for (int kk=0; kk<dd; kk++) {
      memcpy(dest, sjv[kk]+(jj*ww+ii)*bz, bz);
      dest += bz;
    }
  }
}

void Context::reorderThread(t_reorder_arg* targ, char* data, 
			    void* proc(void*), int* cnt)
{
  for (int mm=0; mm<naxis_[2]; mm++) {
    targ[*cnt].dest = data + (size_t)naxis_[0]*naxis_[1]*mm*targ[*cnt].bz;
    targ[*cnt].mm = mm;
    if (pthread_create(&thread_[*cnt], NULL, proc, &targ[*cnt]))
      internalError("Unable to Create Thread");

    (*cnt)++;
    if (*cnt == parent_->nthreads_) {
      for (int pp=0; pp<*cnt; pp++) {
	if (pthread_join(thread_[pp], NULL))
	  internalError("Unable to Join Thread");
      }
      *cnt =0;
    }
  }
}

void Context::reorderAxes()
{
  int bitpix = bfits_->baseFile()->head()->bitpix();
  int bz = abs(bitpix)/8;

  int ww = bfits_->baseFile()->head()->naxis(0);
  int hh = bfits_->baseFile()->head()->naxis(1);
  int dd = baxis_[2];
  size_t sz = (size_t)ww*hh*dd*bz;

  // Data
  char* data = new char[sz];
  if (!data)
    return;
  memset(data,sz,0);

  naxis_ = new int[FTY_MAXAXES];
  for (int ii=0; ii<FTY_MAXAXES; ii++)
    naxis_[ii] =1;

  // slice jump vector
  char* sjv[dd];
  FitsImage* sptr = bfits_;
  int ii=0;
  while (sptr) {
    sjv[ii++] = (char*)sptr->basedata();
    if (sptr)
      sptr = sptr->nextSlice();
  }

  if (DebugPerf)
    cerr << "Context::start reorder...";

  t_reorder_arg* targ = new t_reorder_arg[parent_->nthreads_];

  if (thread_)
    delete [] thread_;
  thread_ = new pthread_t[parent_->nthreads_];

  for (int ii=0; ii<parent_->nthreads_; ii++) {
    targ[ii].sjv = sjv;
    targ[ii].ww = ww;
    targ[ii].hh = hh;
    targ[ii].dd = dd;
    targ[ii].bz = bz;
  }

  int cnt =0;
  switch (axesOrder_) {
  case 123:
    return;
  case 132:
    naxis_[0] =ww;
    naxis_[1] =dd;
    naxis_[2] =hh;
    reorderThread(targ, data, reorder132, &cnt);
    break;
  case 213:
    naxis_[0] =hh;
    naxis_[1] =ww;
    naxis_[2] =dd;
    reorderThread(targ, data, reorder213, &cnt);
    break;
  case 231:
    naxis_[0] =hh;
    naxis_[1] =dd;
    naxis_[2] =ww;
    reorderThread(targ, data, reorder231, &cnt);
    break;
  case 312:
    naxis_[0] =dd;
    naxis_[1] =ww;
    naxis_[2] =hh;
    reorderThread(targ, data, reorder312, &cnt);
    break;
  case 321:
    naxis_[0] =dd;
    naxis_[1] =hh;
    naxis_[2] =ww;
    reorderThread(targ, data, reorder321, &cnt);
    break;
  }

  for (int pp=0; pp<cnt; pp++)
    if (pthread_join(thread_[pp], NULL))
      internalError("Unable to Join Thread");

  if (targ)
    delete [] targ;

  if (thread_)
    delete [] thread_;
  thread_ =NULL;

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

  // Header
  FitsHead* hdr = new FitsHead(*(bfits_->baseFile()->head()));
  hdr->setInteger("NAXES", 3, "");
  hdr->setInteger("NAXIS1", naxis_[0], "");
  hdr->setInteger("NAXIS2", naxis_[1], "");
  if (hdr->find("NAXIS3"))
    hdr->setInteger("NAXIS3", naxis_[2], "");
  else {
    char* n2= hdr->find("NAXIS2");
    hdr->insertInteger("NAXIS3", naxis_[2], "", n2);
  }

  for (int ii=0; ii<MULTWCS; ii++) {
    char alt = (ii==0) ? ' ' : (char)('@'+ii);

    reorderWCSi(hdr, "CROTA  ", 5, alt); // real

    reorderWCSi(hdr, "CRVAL  ", 5, alt); // real
    reorderWCSi(hdr, "CRPIX  ", 5, alt); // real
    reorderWCSi(hdr, "CDELT  ", 5, alt); // real
    reorderWCSi(hdr, "CTYPE  ", 5, alt); // str
    reorderWCSi(hdr, "CUNIT  ", 5, alt); // str
    reorderWCSi(hdr, "CRDER  ", 5, alt); // real
    reorderWCSi(hdr, "CSYER  ", 5, alt); // real

    reorderWCSij(hdr, "CD _  ", 2, alt); // real
    reorderWCSij(hdr, "PC _  ", 2, alt); // real
    reorderWCSij(hdr, "PV _  ", 2, alt); // real

    reorderWCSi(hdr, "LTV  ", 3, alt); // real
    reorderWCSij(hdr, "LTM _  ", 3, alt); // real
    reorderWCSi(hdr, "ATV  ", 3, alt); // real
    reorderWCSij(hdr, "ATM _  ", 3, alt); // real
    reorderWCSi(hdr, "DTV  ", 3, alt); // real
    reorderWCSij(hdr, "DTM _  ", 3, alt); // real
  }

  // be sure to have CTYPE1/CTYPE2 or wcs will not process
  if (!hdr->find("CTYPE1") && hdr->find("CTYPE2")) {
    char* c2= hdr->find("CTYPE2");
    hdr->insertString("CTYPE1", "LINEAR", "", c2);
  }
  else if (!hdr->find("CTYPE2") && hdr->find("CTYPE1")) {
    char* c1= hdr->find("CTYPE1");
    hdr->insertString("CTYPE2", "LINEAR", "", c1);
  }

  // Load it
  fits = new FitsImageFitsOrder(parent_, bfits_, hdr, data, sz, 1);
  FitsImage* ptr = fits;
  for (int ii=1; ii<naxis_[2]; ii++) {
    FitsImage* next = 
      new FitsImageFitsNextOrder(parent_, fits, ptr->fitsFile(), ii+1);

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

  // fix z params (in data coords)
  {
    double ff=0;
    double tt=naxis_[2];
    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();
    }
  }

  manageAxes_ =1;
}

void Context::reorderWCSi(FitsHead* hdr, char* kk, int ii, char ww)
{
  char key[8];
  strcpy(key,kk);

  key[ii+1] = ww;

  key[ii] = '1';
  char* c1 = hdr->find(key);
  key[ii] = '2';
  char* c2 = hdr->find(key);
  key[ii] = '3';
  char* c3 = hdr->find(key);

  switch (axesOrder_) {
  case 123:
    return;
  case 132:
    {
      if (c1) {
	key[ii] = '1';
	FitsCard(c1).setKey(key);
      }
      if (c2) {
	key[ii] = '3';
	FitsCard(c2).setKey(key);
      }
      if (c3) {
	key[ii] = '2';
	FitsCard(c3).setKey(key);
      }
    }
    break;
  case 213:
    {
      if (c1) {
	key[ii] = '2';
	FitsCard(c1).setKey(key);
      }
      if (c2) {
	key[ii] = '1';
	FitsCard(c2).setKey(key);
      }
      if (c3) {
	key[ii] = '3';
	FitsCard(c3).setKey(key);
      }
    }
    break;
  case 231:
    {
      if (c1) {
	key[ii] = '3';
	FitsCard(c1).setKey(key);
      }
      if (c2) {
	key[ii] = '1';
	FitsCard(c2).setKey(key);
      }
      if (c3) {
	key[ii] = '2';
	FitsCard(c3).setKey(key);
      }
    }
    break;
  case 312:
    {
      if (c1) {
	key[ii] = '2';
	FitsCard(c1).setKey(key);
      }
      if (c2) {
	key[ii] = '3';
	FitsCard(c2).setKey(key);
      }
      if (c3) {
	key[ii] = '1';
	FitsCard(c3).setKey(key);
      }
    }
    break;
  case 321:
    {
      if (c1) {
	key[ii] = '3';
	FitsCard(c1).setKey(key);
      }
      if (c2) {
	key[ii] = '2';
	FitsCard(c2).setKey(key);
      }
      if (c3) {
	key[ii] = '1';
	FitsCard(c3).setKey(key);
      }
    }
    break;
  }
}

void Context::reorderWCSij(FitsHead* hdr, char* kk, int ii, char ww)
{
  char key[8];
  strcpy(key,kk);

  key[ii+3] = ww;

  key[ii] = '1';
  key[ii+2] = '1';
  char* c11 = hdr->find(key);
  key[ii+2] = '2';
  char* c12 = hdr->find(key);
  key[ii+2] = '3';
  char* c13 = hdr->find(key);

  key[ii] = '2';
  key[ii+2] = '1';
  char* c21 = hdr->find(key);
  key[ii+2] = '2';
  char* c22 = hdr->find(key);
  key[ii+2] = '3';
  char* c23 = hdr->find(key);

  key[ii] = '3';
  key[ii+2] = '1';
  char* c31 = hdr->find(key);
  key[ii+2] = '2';
  char* c32 = hdr->find(key);
  key[ii+2] = '3';
  char* c33 = hdr->find(key);

  switch (axesOrder_) {
  case 123:
    return;
  case 132:
    {
      if (c11) {
	key[ii] = '1';
	key[ii+2] = '1';
	FitsCard(c11).setKey(key);
      }
      if (c12) {
	key[ii] = '1';
	key[ii+2] = '3';
	FitsCard(c12).setKey(key);
      }
      if (c13) {
	key[ii] = '1';
	key[ii+2] = '2';
	FitsCard(c13).setKey(key);
      }

      if (c21) {
	key[ii] = '3';
	key[ii+2] = '1';
	FitsCard(c21).setKey(key);
      }
      if (c22) {
	key[ii] = '3';
	key[ii+2] = '3';
	FitsCard(c22).setKey(key);
      }
      if (c23) {
	key[ii] = '3';
	key[ii+2] = '2';
	FitsCard(c23).setKey(key);
      }

      if (c31) {
	key[ii] = '2';
	key[ii+2] = '1';
	FitsCard(c31).setKey(key);
      }
      if (c32) {
	key[ii] = '2';
	key[ii+2] = '3';
	FitsCard(c32).setKey(key);
      }
      if (c33) {
	key[ii] = '2';
	key[ii+2] = '2';
	FitsCard(c33).setKey(key);
      }
    }
    break;
  case 213:
    {
      if (c11) {
	key[ii] = '2';
	key[ii+2] = '2';
	FitsCard(c11).setKey(key);
      }
      if (c12) {
	key[ii] = '2';
	key[ii+2] = '1';
	FitsCard(c12).setKey(key);
      }
      if (c13) {
	key[ii] = '2';
	key[ii+2] = '3';
	FitsCard(c13).setKey(key);
      }

      if (c21) {
	key[ii] = '1';
	key[ii+2] = '2';
	FitsCard(c21).setKey(key);
      }
      if (c22) {
	key[ii] = '1';
	key[ii+2] = '1';
	FitsCard(c22).setKey(key);
      }
      if (c23) {
	key[ii] = '1';
	key[ii+2] = '3';
	FitsCard(c23).setKey(key);
      }

      if (c31) {
	key[ii] = '3';
	key[ii+2] = '2';
	FitsCard(c31).setKey(key);
      }
      if (c32) {
	key[ii] = '3';
	key[ii+2] = '1';
	FitsCard(c32).setKey(key);
      }
      if (c33) {
	key[ii] = '3';
	key[ii+2] = '3';
	FitsCard(c33).setKey(key);
      }
    }
    break;
  case 231:
    {
      if (c11) {
	key[ii] = '3';
	key[ii+2] = '3';
	FitsCard(c11).setKey(key);
      }
      if (c12) {
	key[ii] = '3';
	key[ii+2] = '1';
	FitsCard(c12).setKey(key);
      }
      if (c13) {
	key[ii] = '3';
	key[ii+2] = '2';
	FitsCard(c13).setKey(key);
      }

      if (c21) {
	key[ii] = '1';
	key[ii+2] = '3';
	FitsCard(c21).setKey(key);
      }
      if (c22) {
	key[ii] = '1';
	key[ii+2] = '1';
	FitsCard(c22).setKey(key);
      }
      if (c23) {
	key[ii] = '1';
	key[ii+2] = '2';
	FitsCard(c23).setKey(key);
      }

      if (c31) {
	key[ii] = '2';
	key[ii+2] = '3';
	FitsCard(c31).setKey(key);
      }
      if (c32) {
	key[ii] = '2';
	key[ii+2] = '1';
	FitsCard(c32).setKey(key);
      }
      if (c33) {
	key[ii] = '2';
	key[ii+2] = '2';
	FitsCard(c33).setKey(key);
      }
    }
    break;
  case 312:
    {
      if (c11) {
	key[ii] = '2';
	key[ii+2] = '2';
	FitsCard(c11).setKey(key);
      }
      if (c12) {
	key[ii] = '2';
	key[ii+2] = '3';
	FitsCard(c12).setKey(key);
      }
      if (c13) {
	key[ii] = '2';
	key[ii+2] = '1';
	FitsCard(c13).setKey(key);
      }

      if (c21) {
	key[ii] = '3';
	key[ii+2] = '2';
	FitsCard(c21).setKey(key);
      }
      if (c22) {
	key[ii] = '3';
	key[ii+2] = '3';
	FitsCard(c22).setKey(key);
      }
      if (c23) {
	key[ii] = '3';
	key[ii+2] = '1';
	FitsCard(c23).setKey(key);
      }

      if (c31) {
	key[ii] = '1';
	key[ii+2] = '2';
	FitsCard(c31).setKey(key);
      }
      if (c32) {
	key[ii] = '1';
	key[ii+2] = '3';
	FitsCard(c32).setKey(key);
      }
      if (c33) {
	key[ii] = '1';
	key[ii+2] = '1';
	FitsCard(c33).setKey(key);
      }
    }
    break;
  case 321:
    {
      if (c11) {
	key[ii] = '3';
	key[ii+2] = '3';
	FitsCard(c11).setKey(key);
      }
      if (c12) {
	key[ii] = '3';
	key[ii+2] = '2';
	FitsCard(c12).setKey(key);
      }
      if (c13) {
	key[ii] = '3';
	key[ii+2] = '1';
	FitsCard(c13).setKey(key);
      }

      if (c21) {
	key[ii] = '2';
	key[ii+2] = '3';
	FitsCard(c21).setKey(key);
      }
      if (c22) {
	key[ii] = '2';
	key[ii+2] = '2';
	FitsCard(c22).setKey(key);
      }
      if (c23) {
	key[ii] = '2';
	key[ii+2] = '1';
	FitsCard(c23).setKey(key);
      }

      if (c31) {
	key[ii] = '1';
	key[ii+2] = '3';
	FitsCard(c31).setKey(key);
      }
      if (c32) {
	key[ii] = '1';
	key[ii+2] = '2';
	FitsCard(c32).setKey(key);
      }
      if (c33) {
	key[ii] = '1';
	key[ii+2] = '1';
	FitsCard(c33).setKey(key);
      }
    }
    break;
  }

  hdr->buildIndex();
}

void Context::setAxesOrder(int order)
{
  switch (order) {
  case 123:
  case 132:
  case 213:
  case 231:
  case 312:
  case 321:
    axesOrder_ = order;
    break;
  default:
    axesOrder_ = 123;
    break;
  }

  if (bfits_) {
    loadFinish();

    // reset block/analysis
    // since they may have been changed on a reorder cmd
    if (axesOrder_ == 123) {
      if (fits)
	parent_->updateBlock(fits->updateBlock());

      analysis();
    }
  }
}

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::setBlockFactor(const Vector& b) {
  Vector bb = b;
  blockFactor_[0] *= bb[0] <= 0 ? 1 : bb[0];
  blockFactor_[1] *= bb[1] <= 0 ? 1 : bb[1];
}

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

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

void Context::unload()
{
  deleteFits(bfits_);

  if (manageAxes_) {
    delete [] naxis_;
    deleteFits(fits);
    manageAxes_ =0;
  }

  bfits_ =NULL;
  fits =NULL;
  cfits =NULL;

  loadInit(0, Base::NOMOSAIC, Coord::WCS);

  mask.deleteAll();

  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(1, Base::NOMOSAIC,Coord::WCS);
  cfits = fits;

  // 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->isValid()) {
	ptr->setNextSlice(next);
	ptr = next;
	naxis_[2]++;
      }
      else {
	delete next;
	break;
      }
    }
  }

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

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

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

    double zptr = zlim[0];
    FitsImage* ptr = bfits_;
    for (int ii=0; ii<bfits_->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 {
    bfits_->setBinSliceFilter(NULL);
    bfits_->updateFileName();
  }
}

void Context::updateBlock()
{
  if (DebugPerf)
    cerr << "updateBlock " << endl;

  // finish cube of primary
  {
    FitsImage* ptr = fits->nextSlice();
    while (ptr) {
      ptr->load();

      switch (mosaicType) {
      case Base::IRAF:
	ptr->processKeywordsIRAF(fits);
	break;
      case Base::WCSMOSAIC:
	ptr->processKeywordsWCS(fits, mosaicSystem);
	break;
      }

      ptr = ptr->nextSlice();
    }
  }

  // now process any mosaic w/wo cube
  {
    FitsImage* ptr = fits->nextMosaic();
    while (ptr) {
      FitsImage* sptr = ptr;
      while (sptr) {
	sptr->load();

	switch (mosaicType) {
	case Base::IRAF:
	  sptr->processKeywordsIRAF(fits);
	  break;
	case Base::WCSMOSAIC:
	  sptr->processKeywordsWCS(fits, mosaicSystem);
	  break;
	}

	sptr = sptr->nextSlice();
      }
      ptr = ptr->nextMosaic();
    }
  }

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

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();
}

