// 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"
}

typedef struct {
  char* dest;
  char** sjv;
  int ww;
  int hh;
  int dd;
  int bz;
  int mm;
} t_reorder_arg;

ostream& operator<<(ostream& ss, const FitsZBound& bb)
{
  ss << bb.zmin << ' ' << bb.zmax;
  return ss;
}

Context::Context()
{
  parent_ = NULL;

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

  shareWCS_ =0;

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

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

#ifdef __WIN32

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

  FitsImage* ptr = fits;
  while (ptr) {
    FitsImage* sptr = ptr;
    while (sptr) {
      sptr->analysis(doSmooth_);
      sptr = sptr->nextSlice();
    }
    ptr = ptr->nextMosaic();
  }

  updateClip();
}

#else

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

  if (thread_)
    delete [] thread_;
  thread_ = new pthread_t[parent_->nthreads_];
  t_smooth_arg* targ = new t_smooth_arg[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;
    }
  }

  delete [] targ;
  delete [] thread_;
  thread_ =NULL;

  updateClip();
}

#endif

Matrix Context::bin(const Vector& vv)
{
  Matrix mm;
  if (fits) {
    mm = fits->bin(vv);
    binFinish();
  }
  return mm;
}

Matrix Context::binCenter()
{
  Matrix mm;
  if (fits) {
    mm = fits->binCenter();
    binFinish();
  }
  return mm;
}

Matrix Context::binCursor()
{
  Matrix mm;
  if (fits) {
    mm = fits->binCursor();
    binFinish();
  }
  return mm;
}

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

  if (!fits->isHist())
    return;

  // 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
    shareWCS_ =1;
    FitsImage* ptr = fits;
    for (int ii=1; ii<bd; ii++) {
      FitsImage* next = new FitsImageFitsNextHist(this, parent_->interp, fits, ptr->baseFile(), ii+1);
      if (next->isValid()) {
	ptr->setNextSlice(next);
	ptr = next;
	naxis_[2]++;
      }
      else {
	delete next;
	break;
      }
    }
  }

  // fix z params (in data coords)
  iparams.set(0,naxis_[2]);
  cparams.set(0,naxis_[2]);

  frScale.resetScanMode();
  loadFinish();
}

#ifdef __WIN32

int Context::block()
{
  if (DebugPerf)
    cerr << "Context::block()" << endl;

  // primary
  FitsImage* ptr = fits;
  while (ptr) {
    FitsImage* sptr = ptr;
    while (sptr) {
      sptr->block();
      sptr = sptr->nextSlice();
    }
    ptr = ptr->nextMosaic();
  }

  frScale.resetScanMode();

  int rr =1;
  switch (mosaicType) {
  case Base::IRAF:
  case Base::WCSMOSAIC:
    rr = processMosaicKeywords(fits);
    break;
  default:
    break;
  }

  FitsMask* msk = mask.head();
  if (msk) {
    FitsImage* ptr = msk->mask();
    while (ptr) {
      FitsImage* sptr = ptr;
      while (sptr) {
	sptr->block();
      
	switch (mosaicType) {
	case Base::IRAF:
	case Base::WCSMOSAIC:
	  rr &= processMosaicKeywords(ptr);
	  break;
	default:
	  break;
	}
	sptr = sptr->nextSlice();
      }
      ptr = ptr->nextMosaic();
    }
    msk = msk->next();
  }

  return rr & blockMask();
}

int Context::blockMask()
{
  int rr =1;

  FitsMask* msk = mask.head();
  if (msk) {
    FitsImage* ptr = msk->mask();
    while (ptr) {
      FitsImage* sptr = ptr;
      while (sptr) {
	sptr->block();
      
	switch (mosaicType) {
	case Base::IRAF:
	case Base::WCSMOSAIC:
	  rr &= processMosaicKeywords(ptr);
	  break;
	default:
	  break;
	}
	sptr = sptr->nextSlice();
      }
      ptr = ptr->nextMosaic();
    }
    msk = msk->next();
  }

  return rr;
}

#else 

int Context::block()
{
  if (DebugPerf)
    cerr << "Context::block()" << endl;

  int doBlock = (blockFactor_[0] != 1 && blockFactor_[1] != 1) ? 1 : 0;
  int rr =1;

  // primary
  if (thread_)
    delete [] thread_;
  thread_ = new pthread_t[parent_->nthreads_];
  {
    int cnt =0;
    FitsImage* ptr = fits;
    while (ptr) {
      FitsImage* sptr = ptr;
      while (sptr) {
	sptr->block(&thread_[cnt]);
	cnt++;
	if (cnt == parent_->nthreads_) {
	  if (doBlock) {
	    for (int ii=0; ii<cnt; ii++) {
	      int tt = pthread_join(thread_[ii], NULL);
	      if (tt) {
		internalError("Unable to Join Thread");
		rr =0;
	      }
	    }
	  }
	  cnt =0;
	}
	sptr = sptr->nextSlice();
      }
      ptr = ptr->nextMosaic();
    }

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

  frScale.resetScanMode();

  switch (mosaicType) {
  case Base::IRAF:
  case Base::WCSMOSAIC:
    rr &= processMosaicKeywords(fits);
    break;
  default:
    break;
  }

  return rr & blockMask();
}

int Context::blockMask()
{
  int doBlock = (blockFactor_[0] != 1 && blockFactor_[1] != 1) ? 1 : 0;
  int rr =1;

  if (thread_)
    delete [] thread_;
  thread_ = new pthread_t[parent_->nthreads_];
  {
    int cnt =0;

    FitsMask* msk = mask.head();
    if (msk) {
      FitsImage* ptr = msk->mask();
      while (ptr) {
	FitsImage* sptr = ptr;
	while (sptr) {
	  sptr->block(&thread_[cnt]);
	  cnt++;
	  if (cnt == parent_->nthreads_) {
	    if (doBlock) {
	      for (int ii=0; ii<cnt; ii++) {
		int tt = pthread_join(thread_[ii], NULL);
		if (tt) {
		  internalError("Unable to Join Thread");
		  rr =0;
		}
	      }
	    }
	    cnt =0;
	  }
	  sptr = sptr->nextSlice();
	}
	ptr = ptr->nextMosaic();
      }
      msk = msk->next();
    }

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

  {
    FitsMask* msk = mask.head();
    if (msk) {
      FitsImage* ptr = msk->mask();
      while (ptr) {
	FitsImage* sptr = ptr;
	while (sptr) {
	  switch (mosaicType) {
	  case Base::IRAF:
	  case Base::WCSMOSAIC:
	    rr &= processMosaicKeywords(ptr);
	    break;
	  default:
	    break;
	  }
	  sptr = sptr->nextSlice();
	}
	ptr = ptr->nextMosaic();
      }
      msk = msk->next();
    }
  }

  return rr;
}

#endif

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

  switch (frScale.clipScope()) {
  case FrScale::GLOBAL:
    frScale.histogram(fits,num);
    break;
  case FrScale::LOCAL:
    frScale.histogram(cfits,num);
    break;
  }

  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_VOLATILE);

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

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)
{
  if (DebugPerf)
    cerr << "Context::getClip()" << endl;

  // we already have the scale?
  if (frScale.clipMode() == cm && frScale.autoCutPer() == ac)
    return Vector(frScale.low(),frScale.high());

  FrScale cl = frScale;
  cl.setClipMode(cm);
  cl.setAutoCutPer(ac);

  cl.clearHistogram();
  cl.clearHistequ();
  updateClip(&cl);

  // now reset
  updateClip(&frScale);

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

FitsZBound* Context::getDataParams(FrScale::ScanMode which)
{
  switch (which) {
  case FrScale::IMGSEC:
  case FrScale::DATASEC:
    return &iparams;
  case FrScale::CROPSEC:
    return &cparams;
  }
}

Vector Context::getMinMax()
{
  return Vector(frScale.min(), frScale.max());
}

int Context::load(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();
      return 0;
    case Base::MASK:
      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;
    }

    // fix z params (in data coords)
    // do it here because of fits section
    iparams.set(0,baxis_[2]);
    cparams.set(0,baxis_[2]);
    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;

  if (img->nhdu() > 1)
    shareWCS_ =1;

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

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

  // finish up
  img->close();

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

  return 1;
}

int Context::loadExtCube(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(this, parent_->interp, fn, ptr->fitsFile(), FitsFile::NOFLUSH ,1);
      break;
    case Base::ALLOCGZ:
      next = new FitsImageMosaicNextAllocGZ(this, parent_->interp,fn,ptr->fitsFile(), FitsFile::NOFLUSH ,1);
      break;
    case Base::CHANNEL:
      next = new FitsImageMosaicNextChannel(this, parent_->interp,fn,ptr->fitsFile(), FitsFile::NOFLUSH, 1);
      break;
    case Base::MMAP:
      next = new FitsImageMosaicNextMMap(this, parent_->interp, fn, ptr->fitsFile(), 1);
      break;
    case Base::MMAPINCR:
      next = new FitsImageMosaicNextMMapIncr(this, parent_->interp, fn, ptr->fitsFile(), 1);
      break;
    case Base::SHARE:
      next = new FitsImageMosaicNextShare(this, parent_->interp, fn, ptr->fitsFile(), 1);
      break;
    case Base::SOCKET:
      next = new FitsImageMosaicNextSocket(this, parent_->interp, fn,ptr->fitsFile(), FitsFile::FLUSH,1 );
      break;
    case Base::SOCKETGZ:
      next =new FitsImageMosaicNextSocketGZ(this, parent_->interp,fn,ptr->fitsFile(), FitsFile::FLUSH,1 );
      break;
    case Base::VAR:
      next = new FitsImageMosaicNextVar(this, parent_->interp, fn, ptr->fitsFile(), 1);
      break;
    default:
      // na
      break;
    }

    // if previous was a unknown table, delete now, we're done with it
    if (tmp) {
      delete tmp;
      tmp =NULL;
    }

    // first check if fits_ is a known extension, aka we loaded something
    if (next->isImage() || next->isTable()) {
      // now check to see if it resolved into an image, no event tables
      // since any table can be a valid event table
      if (next->isValid() && !next->isHist()) {
	mptr->setNextSlice(next);
	ptr = next;
	mptr = ptr;
	baxis_[2]++;
      }
      else {
	// must be a unknown table, just ignore, and delete next time thru
	ptr = next;
	tmp = next;
      }
    }
    else {
      delete next;
      break;
    }
  }

  // finish up
  img->close();

  // fix z params (in data coords)
  // must wait untill all loaded
  iparams.set(0,baxis_[2]);
  cparams.set(0,baxis_[2]);

  loadFinish();
  return 1;
}

void Context::loadInit(int cnt, Base::MosaicType type, Coord::CoordSystem sys)
{
  shareWCS_ =0;

  mosaicCount_ = cnt;
  mosaicType = type;
  mosaicSystem = sys;
  for (int ii=0; ii<FTY_MAXAXES; ii++) {
    baxis_[ii] =1;
    slice_[ii] =1;
  }
  naxis_ = baxis_;

  // fix z params (in data coords)
  iparams.set(0,1);
  cparams.set(0,1);
}

int Context::loadMosaic(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;
      }

      // fix z params (in data coords)
      // do it here because of fits section
      iparams.set(0,baxis_[2]);
      cparams.set(0,baxis_[2]);
    }
    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;
  }

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

  // get the rest of slices
  if (img->nhdu() > 1)
    shareWCS_ =1;

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

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

  // finish up
  img->close();

  switch (ll) {
  case Base::IMG:
    loadFinishMosaic(fits);
    if (!loadFinish()) {
      unload();
      return 0;
    }
    break;
  case Base::MASK:
    if (!loadFinishMosaicMask())
      return 0;
    break;
  }

  return 1;
}

int Context::loadMosaicImage(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();
      return 0;
    case Base::MASK:
      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;
    }

    // fix z params (in data coords)
    // do it here because of fits section
    iparams.set(0,baxis_[2]);
    cparams.set(0,baxis_[2]);

    break;

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

  Base::MemType sav = which;

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

  if (img->nhdu() > 1)
    shareWCS_ =1;

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

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

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

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

    // if previous was a unknown table, delete now, we're done with it
    if (tmp) {
      delete tmp;
      tmp =NULL;
    }

    // first check if fits_ is a known extension, aka we loaded something
    if (!next->isImage() && !next->isTable()) {
      // ok, we're done
      delete next;
      break;
    }

    // now check to see if it resolved into an image, no event tables
    // since any table can be a valid event table
    if (!next->isValid() || next->isHist()) {
      // must be a unknown table, just ignore, and delete next time thru
	ptr = next;
	tmp = next;
      }
    else {
      ptr->setNextMosaic(next);
      ptr = next;

      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(this, parent_->interp, fn, next->fitsFile(),ii+1);
	  break;
	case Base::ALLOCGZ:
	  snext = new FitsImageFitsNextAllocGZ(this, parent_->interp, fn, next->fitsFile(),ii+1);
	  break;
	case Base::CHANNEL:
	  snext = new FitsImageFitsNextChannel(this, parent_->interp, fn, next->fitsFile(),ii+1);
	  break;
	case Base::MMAP:
	  snext = new FitsImageFitsNextMMap(this, parent_->interp, fn, next->fitsFile(),ii+1);
	  break;
	case Base::SMMAP:
	  snext = new FitsImageFitsNextSMMap(this, parent_->interp, fn, next->fitsFile(),ii+1);
	  break;
	case Base::MMAPINCR:
	  snext = new FitsImageFitsNextMMapIncr(this, parent_->interp, fn, next->fitsFile(),ii+1);
	  break;
	case Base::SHARE:
	  snext = new FitsImageFitsNextShare(this, parent_->interp, fn, next->fitsFile(),ii+1);
	  break;
	case Base::SSHARE:
	  snext = new FitsImageFitsNextSShare(this, parent_->interp, fn, next->fitsFile(),ii+1);
	  break;
	case Base::SOCKET:
	  snext = new FitsImageFitsNextSocket(this, parent_->interp, fn, next->fitsFile(),ii+1);
	  break;
	case Base::SOCKETGZ:
	  snext = new FitsImageFitsNextSocketGZ(this, parent_->interp, fn, next->fitsFile(),ii+1);
	  break;
	case Base::VAR:
	  snext = new FitsImageFitsNextVar(this, parent_->interp, fn, next->fitsFile(),ii+1);
	  break;
	case Base::POST:
	  snext = new FitsImageFitsNextPost(this, parent_->interp, ptr, next->baseFile(),ii+1);
	  break;
	default:
	  // na
	  break;
	}

	if (snext->isValid()) {
	  next->setNextSlice(snext);
	  next = snext;
	}
	else {
	  delete snext;
	  break;
	}
      }
    }
  }
  
  // finish up
  img->close();

  switch (ll) {
  case Base::IMG:
    loadFinishMosaic(fits);
    if (!loadFinish()) {
      unload();
      return 0;
    }
    break;
  case Base::MASK:
    if (!loadFinishMosaicMask())
      return 0;
    break;
  }

  return 1;
}

int Context::loadMosaicWFPC2(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(this, parent_->interp, fn, ptr->fitsFile(), 1);
	break;
      case Base::ALLOCGZ:
	next = new FitsImageFitsNextAllocGZ(this, parent_->interp, fn, ptr->fitsFile(), 1);
	break;
      case Base::CHANNEL:
	next = new FitsImageFitsNextChannel(this, parent_->interp, fn, ptr->fitsFile(), 1);
	break;
      case Base::MMAP:
	next = new FitsImageFitsNextMMap(this, parent_->interp, fn, ptr->fitsFile(), 1);
	break;
      case Base::MMAPINCR:
	next = new FitsImageFitsNextMMapIncr(this, parent_->interp, fn, ptr->fitsFile(), 1);
	break;
      case Base::SHARE:
	next = new FitsImageFitsNextShare(this, parent_->interp, fn, ptr->fitsFile(), 1);
	break;
      case Base::SOCKET:
	next = new FitsImageFitsNextSocket(this, parent_->interp, fn, ptr->fitsFile(), 1);
	break;
      case Base::SOCKETGZ:
	next = new FitsImageFitsNextSocketGZ(this, parent_->interp, fn, ptr->fitsFile(), 1);
	break;
      case Base::VAR:
	next = new FitsImageFitsNextVar(this, parent_->interp, fn, ptr->fitsFile(), 1);
	break;
      case Base::POST:
	next = new FitsImageFitsNextPost(this, parent_->interp, img, ptr->baseFile(), 1);
	break;
      default:
	// na
	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 =NULL;
  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;
  default:
    // na
    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 ii=0; ii<rows; ii++, 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;

      // fix fitsimage params
      ptr->altWCS(istr);

      Matrix mm = parent_->calcAlignWCS(bfits_, ptr, Coord::WCS,
				       Coord::WCS, Coord::FK5);
      ptr->setwcsToRef(mm);

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

  if (table)
    delete table;

  // finish up
  img->close();

  loadFinish();
  return 1;
}

int Context::loadFinish()
{
  if (DebugPerf)
    cerr << "Context::loadFinish()" << endl;

  // 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;

  if (!block())
    return 0;

  FitsImage* ptr = fits;
  while (ptr) {
    FitsImage* sptr = ptr;
    while (sptr) {
      sptr->processKeywordsFitsSection();
      sptr = sptr->nextSlice();
    }
    ptr = ptr->nextMosaic();
  }

  analysis();

  return 1;
}

void Context::loadFinishMask()
{
  FitsMask* msk = mask.tail();
  if (msk) {
    FitsImage* mskimg = msk->mask();
    mskimg->block();
  }
}

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::loadFinishMosaicMask()
{
  FitsMask* msk = mask.tail();
  if (msk)
    loadFinishMosaic(msk->mask());

  return blockMask();
}

int Context::loadSlice(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);
  }

  // finish up
  img->close();

  // fix z params (in data coords)
  // must wait untill all loaded
  iparams.set(0,baxis_[2]);
  cparams.set(0,baxis_[2]);

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

int Context::processMosaicKeywords(FitsImage* ptr)
{
  while (ptr) {
    FitsImage* sptr = ptr;
    while (sptr) {
      switch (mosaicType) {
      case Base::IRAF:
	if (!sptr->processKeywordsIRAF(fits))
	  return 0;
	break;
      case Base::WCSMOSAIC:
	{
	  if (!sptr->hasWCS(mosaicSystem))
	    return 0;

	  Matrix mm = parent_->calcAlignWCS(fits, sptr, mosaicSystem, 
					   mosaicSystem, Coord::FK5);
	  sptr->setwcsToRef(mm);
	}
	break;
      default:
	// na
	break;
      }
      sptr = sptr->nextSlice();
    }
    ptr = ptr->nextMosaic();
  }

  return 1;
}

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;
      }
    */
  }
  return NULL;
}

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;
    }
  }
  return NULL;
}

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;
    }
  }
  return NULL;
}

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;
    }
  }
  return NULL;
}

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;
    }
  }
  return NULL;
}

#ifdef __WIN32

void Context::reorderAxis(char* data, char** sjv, int ww, int hh, int dd, 
			  size_t bz)
{
  t_reorder_arg targ;

  targ.sjv = sjv;
  targ.ww = ww;
  targ.hh = hh;
  targ.dd = dd;
  targ.bz = bz;

  for (int mm=0; mm<naxis_[2]; mm++) {
    targ.dest = data + (size_t)naxis_[0]*naxis_[1]*mm*targ.bz;
    targ.mm = mm;

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

#else

void Context::reorderThread(void* tt, char* data, void* proc(void*), int* cnt)
{
  t_reorder_arg* targ = (t_reorder_arg*)tt;
  
  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::reorderAxis(char* data, char** sjv, int ww, int hh, int dd, 
			  size_t bz)
{
  if (thread_)
    delete [] thread_;
  thread_ = new pthread_t[parent_->nthreads_];
  t_reorder_arg* targ = new t_reorder_arg[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");

  delete [] targ;
  delete [] thread_;
  thread_ =NULL;
}

#endif

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

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

  reorderAxis(data, sjv, ww, hh, dd, bz);

  // 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, (char*)"CROTA  ", 5, alt); // real

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

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

    reorderWCSi(hdr, (char*)"LTV  ", 3, alt); // real
    reorderWCSij(hdr, (char*)"LTM _  ", 3, alt); // real
    reorderWCSi(hdr, (char*)"ATV  ", 3, alt); // real
    reorderWCSij(hdr, (char*)"ATM _  ", 3, alt); // real
    reorderWCSi(hdr, (char*)"DTV  ", 3, alt); // real
    reorderWCSij(hdr, (char*)"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(this, parent_->interp, bfits_, hdr, data, sz, 1);
  FitsImage* ptr = fits;
  for (int ii=1; ii<naxis_[2]; ii++) {
    FitsImage* next = new FitsImageFitsNextOrder(this, parent_->interp, fits, ptr->fitsFile(), ii+1);

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

  // fix z params (in data coords)
  iparams.set(0,naxis_[2]);
  cparams.set(0,naxis_[2]);

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

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];
}

Vector Context::setBlockFactor(const Vector& b) {
  Vector bb = b;
  Vector old = blockFactor_;
  blockFactor_[0] *= bb[0] <= 0 ? 1 : bb[0];
  blockFactor_[1] *= bb[1] <= 0 ? 1 : bb[1];

  return Vector(old[0]/blockFactor_[0],old[1]/blockFactor_[1]);
}

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

  return Vector(old[0]/blockFactor_[0],old[1]/blockFactor_[1]);
}

void Context::setCrop3dParams()
{
  // params is a BBOX in DATA coords 0-n
  cparams = iparams;
}

void Context::setCrop3dParams(double z0, double z1)
{
  // params is a BBOX in DATA coords 0-n
  double zmin = z0;
  double zmax = z1;

  // always have at least 1
  if (zmin+1>zmax)
    zmax = z0+1;

  // round to int
  cparams.set(int(zmin+.5), int(zmax+.5));
}

void Context::setCrop3dParams(int z0, int z1)
{
  // params is a BBOX in DATA coords 0-n
  if (z0<iparams.zmin) {
    z0=iparams.zmin;
    if (z0+1>z1)
      z1=z0+1;
  }

  if (z1>iparams.zmax) {
    z1=iparams.zmax;
    if (z0+1>z1)
      z0=z1-1;
  }

  cparams.set(z0,z1);
}

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

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

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

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

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

void Context::updateClip(FrScale* fr)
{
  if (DebugPerf)
    cerr << "Context::updateClip(FrScale*)" << 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);

  switch (fr->clipScope()) {
  case FrScale::GLOBAL:
    {
      FitsImage* ptr = fits;
      while (ptr) {
	FitsImage* sptr = ptr;
	while (sptr) {
	  sptr->updateClip(fr);

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

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

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

	  sptr = sptr->nextSlice();
	}

	ptr = ptr->nextMosaic();
      }

      // sanity check
      if (fr->min() == DBL_MAX && fr->max() == -DBL_MAX) {
	fr->setMin(NAN);
	fr->setMax(NAN);
      }
      if (fr->low() == DBL_MAX && fr->high() == -DBL_MAX) {
	fr->setLow(NAN);
	fr->setHigh(NAN);
      }

      ptr = fits;
      while (ptr) {
	FitsImage* sptr = ptr;
	while (sptr) {
	  sptr->setClip(fr->low(), fr->high());
	  sptr = sptr->nextSlice();
	}
	ptr = ptr->nextMosaic();
      }
    }
    break;
  case FrScale::LOCAL:
    {
      FitsImage* ptr = cfits;
      while (ptr) {
	ptr->updateClip(fr);

	// find over-all min/max
	if (fr->min() > ptr->min())
	  fr->setMin(ptr->min());
      
	if (fr->max() < ptr->max())
	  fr->setMax(ptr->max());

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

	if (fr->high() < ptr->high())
	  fr->setHigh(ptr->high());

	ptr = ptr->nextMosaic();
      }
    }

    // sanity check
    if (fr->min() == DBL_MAX && fr->max() == -DBL_MAX) {
      fr->setMin(NAN);
      fr->setMax(NAN);
    }

    if (fr->low() == DBL_MAX && fr->high() == -DBL_MAX) {
      fr->setLow(NAN);
      fr->setHigh(NAN);
    }

    break;
  }

  if (DebugPerf)
    cerr << *fr << endl;
}

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

void Context::updateContoursScale()
{
  if (contour) {
    InverseScale* scale =NULL;
    int numlevel = contour->numLevel();
    Vector limits(frScale.low(),frScale.high());
    FrScale::ColorScaleType colorScaleType = (FrScale::ColorScaleType)contour->colorScaleType();
    float expo = frScale.expo();

    switch (colorScaleType) {
    case FrScale::LINEARSCALE:
      scale = new LinearInverseScale(numlevel, limits[0], limits[1]);
      break;
    case FrScale::LOGSCALE:
      scale =  new LogInverseScale(numlevel, limits[0], limits[1], expo);
      break;
    case FrScale::POWSCALE:
      scale =  new PowInverseScale(numlevel, limits[0], limits[1], expo);
      break;
    case FrScale::SQRTSCALE:
      scale =  new SqrtInverseScale(numlevel, limits[0], limits[1]);
      break;
    case FrScale::SQUAREDSCALE:
      scale =  new SquaredInverseScale(numlevel, limits[0], limits[1]);
      break;
    case FrScale::ASINHSCALE:
      scale =  new AsinhInverseScale(numlevel, limits[0], limits[1]);
      break;
    case FrScale::SINHSCALE:
      scale =  new SinhInverseScale(numlevel, limits[0], limits[1]);
      break;
    case FrScale::HISTEQUSCALE:
      scale =  new HistEquInverseScale(numlevel, limits[0], limits[1], 
				       histequ(), HISTEQUSIZE);
      break;
    case FrScale::IISSCALE:
      scale = new IISInverseScale(numlevel, limits[0], limits[1],
				  fits->iisz());
      break;
    }

    ostringstream str;
    str << *scale << ends;
    contour->setLevel(str.str().c_str());
    contour->setLimits(limits);
    contour->update(cfits,scale);
  }
}

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

