/* FILE: util.cc                 -*-Mode: c++-*-
 *
 * Small utility classes
 *
 */

#include <string.h>

#include "oc.h"
#include "nb.h"
#include "threevector.h"
#include "util.h"

/* End includes */

////////////////////////////////////////////////////////////////////////
// Oxs_SplitList
void Oxs_SplitList::Release()
{
  if(argv!=NULL) {
    Tcl_Free((char *)argv);
    argv=NULL;
  }
  argc=0;
}

Oxs_SplitList::~Oxs_SplitList()
{
  Release();
}

int Oxs_SplitList::Split(const char* argstr)
{
  Release();
  char *str = new char[strlen(argstr)+1];
  strcpy(str,argstr);
  int errcode=Tcl_SplitList(NULL,str,&argc,&argv);
  delete[] str;
  return errcode;
}

void Oxs_SplitList::FillParams(vector<string>& params) const
{
  params.clear();
  for(int i=0;i<argc;i++) params.push_back(string(argv[i]));
}


const char* Oxs_SplitList::operator[](int n) const
{
  if(n<0 || n>=argc || argv==NULL) return NULL;
  return argv[n];
}

#if (TCL_MAJOR_VERSION > 8 || (TCL_MAJOR_VERSION==8 && TCL_MINOR_VERSION>0))
////////////////////////////////////////////////////////////////////////
// Wrapper for Tcl_Obj**
Oxs_TclObjArray::Oxs_TclObjArray(int arrsize)
{
  if(arrsize<0) {
    char buf[512];
    Oc_Snprintf(buf,sizeof(buf),
		"Oxs_TclObjArray constructor: "
		"Invalid arrsize=%d (must be non-negative)",
		arrsize);
    OXS_THROW(Oxs_BadIndex,buf);
  }
  size=arrsize;
  arr = new Tcl_Obj*[size];
  for(int i=0;i<size;i++) {
    arr[i] = Tcl_NewObj();
    Tcl_IncrRefCount(arr[i]);
  }
}

Oxs_TclObjArray::~Oxs_TclObjArray()
{
  for(int i=0;i<size;i++) {
    Tcl_DecrRefCount(arr[i]);
  }
  delete[] arr;
}

OxsTclObjConstPtrConstRef Oxs_TclObjArray::operator[](int index) const
{
#ifndef NDEBUG
  if(index>=size || index<0) {
    char buf[512];
    Oc_Snprintf(buf,sizeof(buf),
		"const Tcl_Obj*& Oxs_TclObjArray::operator[] const: "
		"Array out-of-bounds; index=%d, size=%d",
		index,size);
    OXS_THROW(Oxs_BadIndex,buf);
  }
#endif
  return static_cast<OxsTclObjConstPtrConstRef>(arr[index]);
  /// Brute-force the cast, which assumes that there is no storage
  /// difference between TclObj* and const TclObj*, at least in
  /// the indicated direction.  This cast is needed by some compilers,
  /// which otherwise return a reference to a temporary, which is
  /// definitely *not* what we want!
}

OxsTclObjPtrRef Oxs_TclObjArray::operator[](int index)
{
#ifndef NDEBUG
  if(index>=size || index<0) {
    char buf[512];
    Oc_Snprintf(buf,sizeof(buf),
		"Tcl_Obj*& Oxs_TclObjArray::operator[]: "
		"Array out-of-bounds; index=%d, size=%d",
		index,size);
    OXS_THROW(Oxs_BadIndex,buf);
  }
#endif
  return arr[index];
}

// Write interface routines.  These handle the 
// copy-on-write semantics of the Tcl referencing
// system.  We can included additional routines
// when needed.
void
Oxs_TclObjArray::WriteString(int index,const char* cptr)
{
#ifndef NDEBUG
  if(index>=size || index<0) {
    char buf[512];
    Oc_Snprintf(buf,sizeof(buf),
		"void Oxs_TclObjArray::WriteString: "
		"Array out-of-bounds; index=%d, string=%s",
		index,cptr);
    OXS_THROW(Oxs_BadIndex,buf);
  }
#endif
  Tcl_Obj* obj = arr[index];
  if (Tcl_IsShared(obj)) {
    Tcl_Obj* tmp = obj;
    arr[index] = obj = Tcl_DuplicateObj(obj); // Create copy to write on
    Tcl_IncrRefCount(obj);
    Tcl_DecrRefCount(tmp);
  }
  Tcl_SetStringObj(obj,
		   Oc_AutoBuf(cptr),
		   static_cast<int>(strlen(cptr)));
}

void
Oxs_TclObjArray::WriteString(int index,const string& str)
{
  WriteString(index,str.c_str());
}

void
Oxs_TclObjArray::WriteDouble(int index,double val)
{
#ifndef NDEBUG
  if(index>=size || index<0) {
    char buf[512];
    Oc_Snprintf(buf,sizeof(buf),
		"void Oxs_TclObjArray::WriteDouble: "
		"Array out-of-bounds; index=%d, double=%g",
		index,val);
    OXS_THROW(Oxs_BadIndex,buf);
  }
#endif
  Tcl_Obj* obj = arr[index];
  if (Tcl_IsShared(obj)) {
    Tcl_Obj* tmp = obj;
    arr[index] = obj = Tcl_DuplicateObj(obj); // Create copy to write on
    Tcl_IncrRefCount(obj);
    Tcl_DecrRefCount(tmp);
  }
  Tcl_SetDoubleObj(obj,val);
}

void
Oxs_TclObjArray::WriteInt(int index,int val)
{
#ifndef NDEBUG
  if(index>=size || index<0) {
    char buf[512];
    Oc_Snprintf(buf,sizeof(buf),
		"void Oxs_TclObjArray::WriteInt: "
		"Array out-of-bounds; index=%d, int=%ld",
		index,val);
    OXS_THROW(Oxs_BadIndex,buf);
  }
#endif
  Tcl_Obj* obj = arr[index];
  if (Tcl_IsShared(obj)) {
    Tcl_Obj* tmp = obj;
    arr[index] = obj = Tcl_DuplicateObj(obj); // Create copy to write on
    Tcl_IncrRefCount(obj);
    Tcl_DecrRefCount(tmp);
  }
  Tcl_SetIntObj(obj,val);
}

void
Oxs_TclObjArray::WriteLong(int index,long val)
{
#ifndef NDEBUG
  if(index>=size || index<0) {
    char buf[512];
    Oc_Snprintf(buf,sizeof(buf),
		"void Oxs_TclObjArray::WriteLong: "
		"Array out-of-bounds; index=%d, long=%ld",
		index,val);
    OXS_THROW(Oxs_BadIndex,buf);
  }
#endif
  Tcl_Obj* obj = arr[index];
  if (Tcl_IsShared(obj)) {
    Tcl_Obj* tmp = obj;
    arr[index] = obj = Tcl_DuplicateObj(obj); // Create copy to write on
    Tcl_IncrRefCount(obj);
    Tcl_DecrRefCount(tmp);
  }
  Tcl_SetLongObj(obj,val);
}

string
Oxs_TclObjArray::GetString(int index) const
{
#ifndef NDEBUG
  if(index>=size || index<0) {
    char buf[512];
    Oc_Snprintf(buf,sizeof(buf),
		"string Oxs_TclObjArray::GetString: "
		"Array out-of-bounds; index=%d",
		index);
    OXS_THROW(Oxs_BadIndex,buf);
  }
#endif
  return string(Tcl_GetString(arr[index]));
}
#endif // Tcl version check

////////////////////////////////////////////////////////////////////////
// Wrappers for Tcl_Merge().
string Oxs_MergeList(const vector<string>* args)
{
  string result;
  if(args==NULL || args->size()<1) {
    return result; // Return empty string
  }

  // Life would be so much simpler if Tcl would use const char*
  // instead of char* in parameter lists.  Sigh...
  int argc = args->size();
  Oc_AutoBuf* mybuf = new Oc_AutoBuf[argc];
  char **argv = new char*[argc];
  for(int i=0;i<argc;i++) {
    mybuf[i].Dup((*args)[i].c_str());
    argv[i] = mybuf[i].GetStr();
  }

  char* merged_string = Tcl_Merge(argc,argv);
  result = merged_string; // Copy into result string
  Tcl_Free(merged_string); // Free memory allocated inside Tcl_Merge()

  delete[] argv;
  delete[] mybuf;

  return result;
}

string Oxs_MergeList(const vector<string>& args)
{
  return Oxs_MergeList(&args);
}

#if (TCL_MAJOR_VERSION > 8 || (TCL_MAJOR_VERSION==8 && TCL_MINOR_VERSION>0))
string Oxs_MergeList(const Oxs_TclObjArray& arr)
{
  string result;
  int argc = arr.Size();
  if(argc<1) {
    return result; // Return empty string
  }

  // Life would be so much simpler if Tcl would use const char*
  // instead of char* in parameter lists.  Sigh...
  Oc_AutoBuf* mybuf = new Oc_AutoBuf[argc];
  char **argv = new char*[argc];
  for(int i=0;i<argc;i++) {
    mybuf[i].Dup(arr.GetString(i).c_str());
    argv[i] = mybuf[i].GetStr();
  }

  char* merged_string = Tcl_Merge(argc,argv);
  result = merged_string; // Copy into result string
  Tcl_Free(merged_string); // Free memory allocated inside Tcl_Merge()

  delete[] argv;
  delete[] mybuf;

  return result;
}
#endif // Tcl version check

////////////////////////////////////////////////////////////////////////
// Oxs_FilePointer
Oxs_FilePointer::Oxs_FilePointer
(const char* filename,const char* filemode)
{
  fptr = fopen(filename,filemode);
  // Throw exception here if fptr == NULL ?
}

Oxs_FilePointer::~Oxs_FilePointer()
{
  if(fptr!=NULL) fclose(fptr);
}

////////////////////////////////////////////////////////////////////////
// Oxs_Box
void Oxs_Box::Set(REAL8m x0p,REAL8m x1p,
		  REAL8m y0p,REAL8m y1p,
		  REAL8m z0p,REAL8m z1p)
{ // This Set function insures that x0<=x1, y0<=y1, z0<=z1.
  if(x0p<=x1p) { x0=x0p; x1=x1p; }
  else         { x0=x1p; x1=x0p; }
  if(y0p<=y1p) { y0=y0p; y1=y1p; }
  else         { y0=y1p; y1=y0p; }
  if(z0p<=z1p) { z0=z0p; z1=z1p; }
  else         { z0=z1p; z1=z0p; }
}

void Oxs_Box::Set(const string& sx0,const string& sx1,
		  const string& sy0,const string& sy1,
		  const string& sz0,const string& sz1)
{ // This Set function insures that x0<=x1, y0<=y1, z0<=z1.
  REAL8m x0p,x1p,y0p,y1p,z0p,z1p;
  x0p=Nb_Atof(sx0.c_str());
  x1p=Nb_Atof(sx1.c_str());
  y0p=Nb_Atof(sy0.c_str());
  y1p=Nb_Atof(sy1.c_str());
  z0p=Nb_Atof(sz0.c_str());
  z1p=Nb_Atof(sz1.c_str());
  Set(x0p,x1p,y0p,y1p,z0p,z1p);
}

BOOL
Oxs_Box::CheckOrderSet(REAL8m xmin,REAL8m xmax,
		       REAL8m ymin,REAL8m ymax,
		       REAL8m zmin,REAL8m zmax)
{ // Checks that [xyz]min <= [xyz]max.  Returns 0 if not,
  // otherwise sets [xyz][01] and returns 1.
  if(xmax<xmin || ymax<ymin || zmax<zmin) return 0;
  x0=xmin;   x1=xmax;
  y0=ymin;   y1=ymax;
  z0=zmin;   z1=zmax;
  return 1;
}

BOOL Oxs_Box::Expand(const Oxs_Box& other)
{ // Expands *this as necessary to contain region specified by "other".
  // Returns 1 if *this was enlarged, 0 if other was already contained in
  // *this.
  if(other.IsEmpty()) return 0; // "other" is an empty box
  if(IsEmpty()) {
    // *this is an empty box
    *this = other;
    return 1; // We only get to this clause if other
    /// is not empty.
  }
  BOOL expanded=0;
  if(other.x0<x0) { x0 = other.x0; expanded=1; }
  if(other.y0<y0) { y0 = other.y0; expanded=1; }
  if(other.z0<z0) { z0 = other.z0; expanded=1; }
  if(other.x1>x1) { x1 = other.x1; expanded=1; }
  if(other.y1>y1) { y1 = other.y1; expanded=1; }
  if(other.z1>z1) { z1 = other.z1; expanded=1; }
  return expanded;
}

BOOL Oxs_Box::Expand(REAL8m x,REAL8m y,REAL8m z)
{ // Similar to Expand(const Oxs_Box&), but with a single point import
  // instead of a box.  Returns 1 if *this was enlarged, 0 if (x,y,z)
  // was already contained in *this.
  if(IsEmpty()) {
    // *this is an empty box
    x0=x1=x;
    y0=y1=y;
    z0=z1=z;
    return 1;
  }
  BOOL expanded=0;
  if(x<x0) { x0 = x; expanded=1; }
  if(y<y0) { y0 = y; expanded=1; }
  if(z<z0) { z0 = z; expanded=1; }
  if(x>x1) { x1 = x; expanded=1; }
  if(y>y1) { y1 = y; expanded=1; }
  if(z>z1) { z1 = z; expanded=1; }
  return expanded;
}

BOOL Oxs_Box::Intersect(const Oxs_Box& other)
{ // Shrinks *this as necessary so that the resulting box
  // is the intersection of the original *this with other.
  // Returns 1 if *this is shrunk, 0 if *this is already
  // contained inside other.
  if(IsEmpty()) return 0; // *this is already empty
  if(other.IsEmpty()) { // "other" is an empty box
    MakeEmpty();
    return 1; // We only get to this clause if *this
    /// was not empty coming in.
  }
  BOOL shrunk=0;
  if(other.x0>x0) { x0 = other.x0; shrunk=1; }
  if(other.y0>y0) { y0 = other.y0; shrunk=1; }
  if(other.z0>z0) { z0 = other.z0; shrunk=1; }
  if(other.x1<x1) { x1 = other.x1; shrunk=1; }
  if(other.y1<y1) { y1 = other.y1; shrunk=1; }
  if(other.z1<z1) { z1 = other.z1; shrunk=1; }
  return shrunk;
}

BOOL Oxs_Box::IsIn(const ThreeVector& point) const
{
  if(point.x<x0 || point.x>x1 ||
     point.y<y0 || point.y>y1 ||
     point.z<z0 || point.z>z1) return 0;
  return 1;
}

BOOL Oxs_Box::IsContained(const Oxs_Box& other) const
{
  if(other.x0>other.x1) return 1; // "other" is an empty box
  if(x0>x1) return 0; // *this is an empty box
  if(other.x0<x0 || other.x1>x1 ||
     other.y0<y0 || other.y1>y1 ||
     other.z0<z0 || other.z1>z1) return 0;
  return 1;
}
