#include "Print.hpp"
#include "IEEEFP.hpp"
#include "FunctionDef.hpp"
#include "Interpreter.hpp"
#include <math.h>
#include <string.h>

#define MSGBUFLEN 2048

FMFormatMode formatMode = format_short;

void SetPrintFormatMode(FMFormatMode m) {
  formatMode = m;
}

FMFormatMode GetPrintFormatMode() {
  return formatMode;
}

class ArrayFormatInfo {
public:
  int width;
  bool floatasint;
  int decimals;
  bool expformat;
  double scalefact;
  ArrayFormatInfo(int w = 0, bool f = false, int d = 0, bool e = false, double s = 1.0) : 
    width(w), floatasint(f), decimals(d), expformat(e), scalefact(s) {}
};

/**
 * Summary of this object when it is an element of a cell array.  This is
 * generally a shorthand summary of the description of the object.
 */
template <class T>
string NumericCellEntry(const T* data, Dimensions dims, string name, 
			const char *formatcode, bool isSparse = false) {
  char msgBuffer[MSGBUFLEN];
  if (dims.isScalar()) {
    snprintf(msgBuffer,MSGBUFLEN,formatcode,data[0]);
    return msgBuffer;
  } else {
    string ret = "[" + dims.asString() + " ";
    if (isSparse)
      ret += " sparse";
    ret += name + "]";
    return ret;
  }
}

template <class T>
string ComplexNumericCellEntry(const T* data, Dimensions dims, string name, 
			       const char *formatcode, bool isSparse = false) {
  char msgBuffer[MSGBUFLEN];
  if (dims.isScalar()) {
    snprintf(msgBuffer,MSGBUFLEN,formatcode,data[0],data[1]);
    return msgBuffer;
  } else {
    string ret = "[" + dims.asString() + " ";
    if (isSparse)
      ret += " sparse";
    ret += name + "]";
    return ret;
  }
}

string SummarizeArrayCellEntry(Array dp)  {
  char msgBuffer[MSGBUFLEN];
  if (dp.isEmpty()) 
    return ("[]");
  else {
    switch(dp.dataClass()) {
    case FM_FUNCPTR_ARRAY:
      return "{" + dp.dimensions().asString() + " function pointer array }";
    case FM_CELL_ARRAY:
      return "{" + dp.dimensions().asString() + " cell }";
    case FM_STRUCT_ARRAY: 
      {
	string ret;
	ret = "[" + dp.dimensions().asString();
	if (dp.isUserClass()) {
	  ret += " " + dp.className().back() + " object";
	} else 
	  ret += " struct array";
	return ret + "]";
      }
    case FM_STRING:
      if (dp.dimensions().getRows() == 1)
	return "['" + dp.getContentsAsString() + "']";
      else
	return "[" + dp.dimensions().asString() + " string ]";
    case FM_LOGICAL:
      return NumericCellEntry((const logical *)dp.getDataPointer(),dp.dimensions(),"logical","[%u]");
    case FM_UINT8:
      return NumericCellEntry((const uint8 *)dp.getDataPointer(),dp.dimensions(),"uint8","[%u]");
    case FM_INT8:
      return NumericCellEntry((const uint8 *)dp.getDataPointer(),dp.dimensions(),"int8","[%d]");
    case FM_UINT16:
      return NumericCellEntry((const uint16 *)dp.getDataPointer(),dp.dimensions(),"uint16","[%u]");
    case FM_INT16:
      return NumericCellEntry((const uint16 *)dp.getDataPointer(),dp.dimensions(),"int16","[%d]");
    case FM_UINT32:
      return NumericCellEntry((const uint32 *)dp.getDataPointer(),dp.dimensions(),"uint32","[%u]");
    case FM_INT32:
      return NumericCellEntry((const uint32 *)dp.getDataPointer(),dp.dimensions(),"int32","[%d]");
    case FM_UINT64:
      return NumericCellEntry((const uint64 *)dp.getDataPointer(),dp.dimensions(),"uint64","[%llu]");
    case FM_INT64:
      return NumericCellEntry((const uint64 *)dp.getDataPointer(),dp.dimensions(),"int64","[%lld]");
    case FM_DOUBLE:
      return NumericCellEntry((const double *)dp.getDataPointer(),dp.dimensions(),"double","[%lg]",dp.sparse());
    case FM_FLOAT:
      return NumericCellEntry((const float *)dp.getDataPointer(),dp.dimensions(),"float","[%g]",dp.sparse());
    case FM_COMPLEX:
      return ComplexNumericCellEntry((const float *)dp.getDataPointer(),dp.dimensions(),
				     "complex","[%g+%gi]",dp.sparse());
    case FM_DCOMPLEX:
      return ComplexNumericCellEntry((const float *)dp.getDataPointer(),dp.dimensions(),
				     "complex","[%lg+%lgi]",dp.sparse());
    }
  }
}
 
template <class T>
bool AllIntegerValues(T *t, int len) {
  if (len == 0) return false;
  bool allInts = true;
  for (int i=0;(i<len) && allInts;i++) {
    allInts = (t[i] == ((T) ((int64) t[i])));
  }
  return allInts;
}

template <class T>
int GetNominalWidthSignedInteger(const T*array, int count) {
  char buffer[MSGBUFLEN];
    
  int maxdigit = 0;
  for (int i=0;i<count;i++) {
    memset(buffer,0,MSGBUFLEN);
    sprintf(buffer,"%lld",(int64)array[i]);
    int j = maxdigit;
    while (buffer[j] && j>=maxdigit)
      j++;
    maxdigit = j;
  }
  return maxdigit;  
}

template <class T>
int GetNominalWidthUnsignedInteger(const T*array, int count) {
  char buffer[MSGBUFLEN];
    
  int maxdigit = 0;
  for (int i=0;i<count;i++) {
    memset(buffer,0,MSGBUFLEN);
    sprintf(buffer,"%llu",(uint64)array[i]);
    int j = maxdigit;
    while (buffer[j] && j>=maxdigit)
      j++;
    maxdigit = j;
  }
  return maxdigit;  
}
 
template <class T>
void ComputeScaleFactor(const T* array, int count, ArrayFormatInfo& format) {
  T max_amplitude;
  if (count == 0) return;
  if (format.expformat) return;
  bool finiteElementFound = false;
  for (int i=0;i<count;i++) {
    if (IsFinite(array[i]) && !finiteElementFound) {
      max_amplitude = array[i];
      finiteElementFound = true;
    }
    if ((IsFinite(array[i])) && 
	(fabs((double) array[i]) > fabs((double) max_amplitude)))
      max_amplitude = array[i];
  }
  if (!finiteElementFound) return;
  if (max_amplitude >= 100)
    format.scalefact = pow(10.0,floor(log10(max_amplitude)));
  else if (max_amplitude <= -100)
    format.scalefact = pow(10.0,floor(log10(-max_amplitude)));
  else if ((max_amplitude <= .1) && (max_amplitude>0))
    format.scalefact = pow(10.0,floor(log10(max_amplitude)));
  else if ((max_amplitude >= -.1) && (max_amplitude<0))
    format.scalefact = pow(10.0,floor(log10(-max_amplitude)));
  else
    format.scalefact = 1.0;
}

ArrayFormatInfo ComputeArrayFormatInfo(const void *dp, int length, Class aclass) {
  switch (aclass) {
  case FM_INT8:    
    return ArrayFormatInfo(GetNominalWidthSignedInteger((const int8*)dp,length));
  case FM_UINT8:   
    return ArrayFormatInfo(GetNominalWidthUnsignedInteger((const uint8*)dp,length));
  case FM_INT16:   
    return ArrayFormatInfo(GetNominalWidthSignedInteger((const int16*)dp,length));
  case FM_UINT16:  
    return ArrayFormatInfo(GetNominalWidthUnsignedInteger((const uint16*)dp,length));
  case FM_INT32:   
    return ArrayFormatInfo(GetNominalWidthSignedInteger((const int32*)dp,length));
  case FM_UINT32:  
    return ArrayFormatInfo(GetNominalWidthUnsignedInteger((const uint32*)dp,length));
  case FM_INT64:   
    return ArrayFormatInfo(GetNominalWidthSignedInteger((const int64*)dp,length));
  case FM_UINT64:  
    return ArrayFormatInfo(GetNominalWidthUnsignedInteger((const uint64*)dp,length));
  case FM_LOGICAL: 
    return ArrayFormatInfo(1);
  case FM_STRING:  
    return ArrayFormatInfo(1);
  case FM_FLOAT:   
    if (AllIntegerValues((const float*)dp,length)) 
      return ArrayFormatInfo(GetNominalWidthSignedInteger((const float*)dp,length),
			     true);
    else {
      ArrayFormatInfo ret;
      if (formatMode == format_short) 
 	ret = ArrayFormatInfo(9,false,4);
      else if (formatMode == format_long)
 	ret = ArrayFormatInfo(11,false,7);
      else if (formatMode == format_short_e)
 	ret = ArrayFormatInfo(11,false,4,true);
      else if (formatMode == format_long_e)
 	ret = ArrayFormatInfo(14,false,7,true);
      ComputeScaleFactor((const float*) dp,length,ret);
      return ret;
    }
  case FM_DOUBLE:  
    if (AllIntegerValues((const double*)dp,length)) 
      return ArrayFormatInfo(GetNominalWidthSignedInteger((const double*)dp,length),
  			     true);
    else {
      ArrayFormatInfo ret;
      if (formatMode == format_short)
	ret = ArrayFormatInfo(9,false,4);
      else if (formatMode == format_long)
	ret = ArrayFormatInfo(18,false,14);
      else if (formatMode == format_short_e)
	ret = ArrayFormatInfo(11,false,4,true);
      else if (formatMode == format_long_e)
	ret = ArrayFormatInfo(21,false,14,true);
      ComputeScaleFactor((const double*) dp, length, ret);
      return ret;
    }
  case FM_COMPLEX:
    {
      ArrayFormatInfo ret;
      if (formatMode == format_short)
 	ret = ArrayFormatInfo(19,false,4);
      else if (formatMode == format_long)
 	ret = ArrayFormatInfo(23,false,7);
      else if (formatMode == format_short_e)
 	ret = ArrayFormatInfo(19,false,4,true);
      else if (formatMode == format_long_e)
 	ret = ArrayFormatInfo(23,false,7,true);
      ComputeScaleFactor((const float*) dp,length*2,ret);
      return ret;
    }
  case FM_DCOMPLEX:  
    {
      ArrayFormatInfo ret;
      if (formatMode == format_short)
 	ret = ArrayFormatInfo(19,false,4);
      else if (formatMode == format_long)
 	ret = ArrayFormatInfo(37,false,14);
      else if (formatMode == format_short_e)
 	ret = ArrayFormatInfo(19,false,4,true);
      else if (formatMode == format_long_e)
 	ret = ArrayFormatInfo(37,false,14,true);
      ComputeScaleFactor((const double*) dp,length*2,ret);
      return ret;
    }
  case FM_CELL_ARRAY: 
    {
      int maxwidth = 1;
      const Array *ap = (const Array *) dp;
      for (int i=0;i<length;i++) {
	int len = SummarizeArrayCellEntry(ap[i]).size();
	maxwidth = max(maxwidth,len);
      }
      return ArrayFormatInfo(maxwidth);
    }
  case FM_FUNCPTR_ARRAY:
    return ArrayFormatInfo(20);
  }  
}

template <class T>
void emitSignedInteger(Interpreter* io, T val, const ArrayFormatInfo &format) {
  io->outputMessage("%*lld",format.width,(int64)val);
}

template <class T>
void emitUnsignedInteger(Interpreter* io, T val, const ArrayFormatInfo &format) {
  io->outputMessage("%*llu",format.width,(uint64)val);
}

template <class T>
void emitFloat(Interpreter*io, T val, const ArrayFormatInfo &format) {
  if (val != 0) 
    if (format.expformat)
      io->outputMessage("%*.*e",format.width,format.decimals,val);
    else
      io->outputMessage("%*.*f",format.width,format.decimals,val/format.scalefact);
  else
    io->outputMessage("%*d",format.width,0);
}

template <class T>
void emitComplex(Interpreter* io, T real, T imag, const ArrayFormatInfo &format) {
  int width = format.width/2;
  if ((real != 0) || (imag != 0)) {
    if (format.expformat)
      io->outputMessage("%*.*e",width,format.decimals,real/format.scalefact);
    else
      io->outputMessage("%*.*f",width,format.decimals,real/format.scalefact);
    if (imag < 0) {
      if (format.expformat)
	io->outputMessage(" -%*.*ei",width-1,format.decimals,-imag/format.scalefact);
      else
	io->outputMessage(" -%*.*fi",width-1,format.decimals,-imag/format.scalefact);
    } else {
      if (format.expformat)
	io->outputMessage(" +%*.*ei",width-1,format.decimals,imag/format.scalefact);
      else
	io->outputMessage(" +%*.*fi",width-1,format.decimals,imag/format.scalefact);
    }
  } else 
    io->outputMessage("%*d%*c",width,0,width+2,' ');
}

void emitFormattedElement(Interpreter* io, const void *dp, 
			  const ArrayFormatInfo &format, int num, Class dcls) {
  switch (dcls) {
  case FM_INT8:   
    emitSignedInteger(io,((const int8*) dp)[num],format);
    return;
  case FM_INT16:  
    emitSignedInteger(io,((const int16*) dp)[num],format);
    return;
  case FM_INT32:  
    emitSignedInteger(io,((const int32*) dp)[num],format);
    return;
  case FM_INT64:  
    emitSignedInteger(io,((const int64*) dp)[num],format);
    return;
  case FM_UINT8:  
    emitUnsignedInteger(io,((const uint8*) dp)[num],format);
    return;
  case FM_UINT16: 
    emitUnsignedInteger(io,((const uint16*) dp)[num],format);
    return;
  case FM_UINT32: 
    emitUnsignedInteger(io,((const uint32*) dp)[num],format);
    return;
  case FM_UINT64: 
    emitUnsignedInteger(io,((const uint64*) dp)[num],format);
    return;
  case FM_LOGICAL:
    emitUnsignedInteger(io,((const logical*) dp)[num],format);
    return;
  case FM_FLOAT: 
    if (format.floatasint)
      emitSignedInteger(io,((const float*) dp)[num],format);
    else 
      emitFloat(io,((const float*) dp)[num],format);
    return;
  case FM_DOUBLE: 
    if (format.floatasint)
      emitSignedInteger(io,((const double*) dp)[num],format);
    else
      emitFloat(io,((const double*) dp)[num],format);
    return;
  case FM_COMPLEX: 
    emitComplex(io,((const float*) dp)[2*num],((const float*) dp)[2*num+1],format);
    return;
  case FM_DCOMPLEX: 
    emitComplex(io,((const double*) dp)[2*num],((const double*) dp)[2*num+1],format);
    return;
  case FM_STRING:
    io->outputMessage("%c\0",((const char*) dp)[num]);
    return;
  case FM_CELL_ARRAY: {
    Array *ap;
    ap = (Array*) dp;
    if (ap == NULL)
      io->outputMessage("[]");
    else
      io->outputMessage(SummarizeArrayCellEntry(ap[num]));
    io->outputMessage("  ");
    break;
  }
  case FM_FUNCPTR_ARRAY: {
    const FuncPtr* ap;
    ap = (const FuncPtr*) dp;
    if (!ap[num]) {
      io->outputMessage("[]  ");
    } else {
      io->outputMessage("@");
      io->outputMessage(ap[num]->name.c_str());
      io->outputMessage("  ");
    }
  }
  }
}

void PrintSheet(Interpreter*io, const ArrayFormatInfo &format, 
		int rows, int columns, int offset, const void* data, 
		Class aclass, int termWidth, int &printlimit) {
  if (printlimit == 0) return;
  // Determine how many columns will fit across
  // the terminal width
  int colsPerPage;
  if (aclass != FM_STRING)
    colsPerPage = (int) floor((termWidth-1)/((double) format.width + 2));
  else
    colsPerPage = (int) floor((termWidth-1)/((double) format.width));
  colsPerPage = (colsPerPage < 1) ? 1 : colsPerPage;
  int pageCount;
  pageCount = (int) ceil(columns/((double)colsPerPage));
  for (int k=0;k<pageCount;k++) {
    int colsInThisPage;
    colsInThisPage = columns - colsPerPage*k;
    colsInThisPage = (colsInThisPage > colsPerPage) ? 
      colsPerPage : colsInThisPage;
    if ((rows*columns > 1) && (pageCount > 1))
      io->outputMessage(" \nColumns %d to %d\n\n",
			k*colsPerPage+1,k*colsPerPage+colsInThisPage);
    else
      io->outputMessage("\n");
    for (int i=0;i<rows;i++) {
      io->outputMessage(" ");
      for (int j=0;j<colsInThisPage;j++) {
	emitFormattedElement(io,data,format,
			     i+(k*colsPerPage+j)*rows+offset,
			     aclass);
	printlimit--;
	if (printlimit <= 0) return;
	if (aclass != FM_STRING) io->outputMessage(" ");
      }
      io->outputMessage("\n");
    }
  }
  io->outputMessage("\n");
}

void PrintArrayClassic(Array A, int printlimit, Interpreter* io) {
  if (printlimit == 0) return;
  int termWidth = io->getTerminalWidth();
  //   bool showClassSize = true; // FINISH
  //   if (!A.isEmpty() && showClassSize)
  //     PrintArrayClassAndSize(A,io);
  Class Aclass(A.dataClass());
  Dimensions Adims(A.dimensions());
  if (A.isUserClass())
    return;
  if (A.isEmpty()) {
    if (A.dimensions().equals(zeroDim))
      io->outputMessage("  []\n");
    else {
      io->outputMessage("  Empty array ");
      A.dimensions().printMe(io);
      io->outputMessage("\n");
    }
    return;
  }
  if (A.sparse()) {
    io->outputMessage("\tMatrix is sparse with %d nonzeros\n",A.getNonzeros());
    return;
  }
  if (Aclass == FM_STRUCT_ARRAY) {
    if (Adims.isScalar()) {
      Array *ap;
      ap = (Array *) A.getDataPointer();
      for (int n=0;n<A.fieldNames().size();n++) {
	io->outputMessage("    ");
	io->outputMessage(A.fieldNames().at(n).c_str());
	io->outputMessage(": ");
	io->outputMessage(SummarizeArrayCellEntry(ap[n]));
	io->outputMessage("\n");
      }
    } else {
      io->outputMessage("  Fields\n");
      for (int n=0;n<A.fieldNames().size();n++) {
	io->outputMessage("    ");
	io->outputMessage(A.fieldNames().at(n).c_str());
	io->outputMessage("\n");
      }
    }
  } else {
    ArrayFormatInfo format(ComputeArrayFormatInfo(A.getDataPointer(),A.getLength(),Aclass));
    if (!A.isScalar() && (format.scalefact != 1))
      io->outputMessage("\n   %.1e * \n",format.scalefact);
    if (A.isScalar() && (format.scalefact != 1) && !format.floatasint)
      format.expformat = true;
    if (Adims.getLength() == 2) {
      int rows = Adims.getRows();
      int columns = Adims.getColumns(); 
      PrintSheet(io,format,rows,columns,0,
		 A.getDataPointer(),Aclass,termWidth,printlimit);
    } else if (Adims.getLength() > 2) {
      /**
       * For N-ary arrays, data slice  -  start with 
       * [1,1,1,...,1].  We keep doing the matrix
       * print , incrementing from the highest dimension,
       * and rolling downwards.
       */
      io->outputMessage("\n");
      Dimensions wdims(Adims.getLength());
      int rows(Adims.getRows());
      int columns(Adims.getColumns());
      int offset = 0;
      while (wdims.inside(Adims)) {
	io->outputMessage("(:,:");
	for (int m=2;m<Adims.getLength();m++) 
	  io->outputMessage(",%d",wdims.get(m)+1);
	io->outputMessage(") = \n");
	PrintSheet(io,format,rows,columns,offset,
		   A.getDataPointer(),Aclass,termWidth,printlimit);
	offset += rows*columns;
	wdims.incrementModulo(Adims,2);
      }
    }
  }
  if (printlimit == 0)
    io->outputMessage("\nPrint limit has been reached.  Use setprintlimit function to enable longer printouts\n");
}

string ArrayToPrintableString(const Array& a) {
  static char msgBuffer[MSGBUFLEN];
  if (a.isEmpty())
    return string("[]");
  if (a.sparse())
    return string("");
  if (a.isString())
    return string(ArrayToString(a));
  if (a.isReferenceType())
    return string("");
  if (!a.isScalar() && !a.isString())
    return string("");
  const void *dp = a.getDataPointer();
  switch (a.dataClass()) {
  case FM_INT8: {
    const int8 *ap;
    ap = (const int8*) dp;
    snprintf(msgBuffer,MSGBUFLEN,"%d",ap[0]);
    return string(msgBuffer);
  }
  case FM_UINT8: {
    const uint8 *ap;
    ap = (const uint8*) dp;
    snprintf(msgBuffer,MSGBUFLEN,"%u",ap[0]);
    return string(msgBuffer);
  }
  case FM_INT16: {
    const int16 *ap;
    ap = (const int16*) dp;
    snprintf(msgBuffer,MSGBUFLEN,"%d",ap[0]);
    return string(msgBuffer);
  }
  case FM_UINT16: {
    const uint16 *ap;
    ap = (const uint16*) dp;
    snprintf(msgBuffer,MSGBUFLEN,"%u",ap[0]);
    return string(msgBuffer);
  }
  case FM_INT32: {
    const int32 *ap;
    ap = (const int32*) dp;
    snprintf(msgBuffer,MSGBUFLEN,"%d",ap[0]);
    return string(msgBuffer);
  }
  case FM_UINT32: {
    const uint32 *ap;
    ap = (const uint32*) dp;
    snprintf(msgBuffer,MSGBUFLEN,"%u",ap[0]);
    return string(msgBuffer);
  }
  case FM_INT64: {
    const int64 *ap;
    ap = (const int64*) dp;
    snprintf(msgBuffer,MSGBUFLEN,"%lld",ap[0]);
    return string(msgBuffer);
  }
  case FM_UINT64: {
    const uint64 *ap;
    ap = (const uint64*) dp;
    snprintf(msgBuffer,MSGBUFLEN,"%llu",ap[0]);
    return string(msgBuffer);
  }
  case FM_LOGICAL: {
    const logical *ap;
    ap = (const logical*) dp;
    snprintf(msgBuffer,MSGBUFLEN,"%d",ap[0]);
    return string(msgBuffer);
  }
  case FM_FLOAT: {
    const float *ap;
    ap = (const float*) dp;
    snprintf(msgBuffer,MSGBUFLEN,"%g",ap[0]);
    return string(msgBuffer);
  }
  case FM_DOUBLE: {
    const double *ap;
    ap = (const double*) dp;
    snprintf(msgBuffer,MSGBUFLEN,"%g",ap[0]);
    return string(msgBuffer);
  }
  case FM_COMPLEX: {
    const float *ap;
    ap = (const float*) dp;
    if (ap[1] > 0)
      snprintf(msgBuffer,MSGBUFLEN,"%g+%g i",ap[0],ap[1]);
    else
      snprintf(msgBuffer,MSGBUFLEN,"%g%g i",ap[0],ap[1]);
    return string(msgBuffer);
  }
  case FM_DCOMPLEX: {
    const double *ap;
    ap = (const double*) dp;
    if (ap[1] > 0)
      snprintf(msgBuffer,MSGBUFLEN,"%g+%g i",ap[0],ap[1]);
    else
      snprintf(msgBuffer,MSGBUFLEN,"%g%g i",ap[0],ap[1]);
    return string(msgBuffer);
  }
  }
  return string("");
}
  


syntax highlighted by Code2HTML, v. 0.9.1