//==============================================================================

//#ifdef WIN32
#   include "stdafx.h"
//#endif

#include "vex.h"
#include <stdlib.h>
#include <string.h>

static char *rcsId = "$Id$";

#ifdef _DEBUG
#  define new DEBUG_NEW
#  undef THIS_FILE
   static char THIS_FILE[] = __FILE__;
#endif

//==============================================================================

int  veTable::load(int index)
{
   return (index >= 0 && index < nLoad) ? loadBins[index] : 0;
}

//------------------------------------------------------------------------------

void veTable::load(int value, int index)
{
   if (index >= 0 && index < nLoad) loadBins[index] = value;
}

//------------------------------------------------------------------------------

int  veTable::rpm (int index)
{
   return (index >= 0 && index < nRPM) ? rpmBins[index] : 0;
}

//------------------------------------------------------------------------------

void veTable::rpm (int value, int index)
{
   if (index >= 0 && index < nRPM) rpmBins[index] = value;
}

//------------------------------------------------------------------------------

int  veTable::ve  (int r, int l)
{
   return (r >= 0 && r < nRPM && l >= 0 && l < nLoad) ? veBins[l*nRPM+r] : 0;
}

//------------------------------------------------------------------------------

void veTable::ve  (int value, int r, int l)
{
   if (r >= 0 && r < nRPM && l >= 0 && l < nLoad) veBins[l*nRPM+r] = value;
}

//------------------------------------------------------------------------------

void veTable::loadType(char *type)
{
   strcpy(loadtype, type);
}

char *veTable::loadType()
{
   return loadtype;
}

//------------------------------------------------------------------------------

void veTable::cleanUp()
{
   if (rpmBins ) { delete [] rpmBins;  rpmBins  = NULL; }
   if (loadBins) { delete [] loadBins; loadBins = NULL; }
   if (veBins  ) { delete [] veBins;   veBins   = NULL; }
}

//------------------------------------------------------------------------------

bool veTable::setSize(int NRPM, int NLoad, int RpmBins[], int LoadBins[], int VeBins[])
{
   cleanUp();

   nRPM     = NRPM;
   rpmBins  = new int [nRPM];
   if (rpmBins == NULL) return false;
   for (int iR = 0; iR < nRPM;  iR++) rpmBins[iR]  = RpmBins  ? RpmBins[iR]  : iR;

   nLoad    = NLoad;
   loadBins = new int [nLoad];
   if (loadBins == NULL) return false;
   for (int iL = 0; iL < nLoad; iL++) loadBins[iL] = LoadBins ? LoadBins[iL] : iL;

   veBins   = new int [nRPM*nLoad];
   if (veBins == NULL) return false;
   for (int iV = 0; iV < nRPM*nLoad; iV++) veBins[iV] = VeBins ? VeBins[iV]  : iV;

   return true;
}

//------------------------------------------------------------------------------

veTable::veTable(int NRPM, int NLoad, int RpmBins[], int LoadBins[], int VeBins[])
 : rpmBins (NULL),
   loadBins(NULL),
   veBins  (NULL)
{
   loadType("MAP");
   setSize(NRPM, NLoad, RpmBins, LoadBins, VeBins);
}

//------------------------------------------------------------------------------

veTable::~veTable()
{
   cleanUp();
}

//==============================================================================

bool vex::closeFile(bool status)
{
   if (fclose(vexFile) != 0) status = false;
   vexFile = NULL;
   return status;
}

//------------------------------------------------------------------------------
//--  Reader                                                                  --

static char *bracketedNumber(char *s, int &n)
{
   s = strchr(s, '[');
   if (s == NULL) return NULL;
   n = strtol(s+1, &s, 10);
   while (strchr(" \t", *s)) s++;
   if (*s != ']') return NULL;
   return s+1;
}

static char *prefix(char *str, char *matches)
{
   int l = strlen(matches); // Do it once.
   return strncmp(str, matches, l) == 0 ? str+l : NULL;
}

bool vex::getLine()
{
   lineBuffer[0] = '\0';
   while (fgets(lineBuffer, sizeof(lineBuffer)-1, vexFile)) {
      char *comment = strchr(lineBuffer, '#');
      if (comment) *comment = '\0';
      if (lineBuffer[0] == '\0') continue; // Ignore empty lines.
      char *eol = strchr(lineBuffer, '\n');
      if (eol) *eol = '\0';
      break;
   }
   return lineBuffer[0] != '\0';
}

bool vex::readTable(int *table, int rows, int cols)
{
   for (int r = 0; r < rows; r++) {
      if (!getLine()) return false;
      int rVal;
      char *s = bracketedNumber(lineBuffer, rVal);
      if (!s) return false;
      if (rVal != r) return false; // Row index in file is not in order.

      s = strchr(s, '='); // Skip the separator between index and values.
      if (!s) return false;
      s++;

      for (int c = 0; c < cols; c++) {
         table[r*cols+c] = strtol(s, &s, 10);
      }
   }
   return true;
}

bool vex::read(const char *fileName)
{
   vexFile = fopen(fileName, "r");
   if (vexFile == NULL) return false;
   if (!fgets(lineBuffer, sizeof(lineBuffer)-1, vexFile)) return closeFile(false);
   if (!prefix(lineBuffer, "EVEME 1.0")) return closeFile(false);

   int     nR = 0;
   int     nL = 0;
   int     *r = NULL; // ??? memory leak on error
   int     *l = NULL;
   int     *v = NULL;
   veTable *p = NULL;
   char     loadType[10];
   char    *tail;

   while (getLine()) {
      if ((tail = prefix(lineBuffer,  "UserRev:"))) {
         UserRev = strtod(tail, NULL) + 0.01;
      }
      else if ((tail = prefix(lineBuffer, "UserComment: "))) {
         userComment(tail);
      }
      else if ((tail = prefix(lineBuffer, "Date: "))) {
         date(tail);
      }
      else if ((tail = prefix(lineBuffer, "Time: "))) {
         time(tail);
      }

      else if ((tail = prefix(lineBuffer, "Page"))) {
         p = newPage(strtol(tail, NULL, 10));
      }

      else if ((tail = prefix(lineBuffer, "VE Table RPM Range"))) {
         if (!bracketedNumber(tail, nR)) return closeFile(false);
         r = new int[nR];
         if (!readTable(r, nR, 1)) return closeFile(false);
      }

      else if ((tail = prefix(lineBuffer, "VE Table Load Range"))) {
         char *lt = strchr(tail, '(');
         if (lt == NULL) return closeFile(false);
         lt++;
         tail = strchr(lt, ')');
         if (tail == NULL) return closeFile(false);
         *tail = '\0';
         strcpy(loadType, lt);
         tail++;

         if (!bracketedNumber(tail, nL)) return closeFile(false);
         l = new int[nL];
         if (!readTable(l, nL, 1)) return closeFile(false);
      }

      else if ((tail = prefix(lineBuffer, "VE Table"))) {
         if (nR == 0 || nL == 0) return closeFile(false);

         // ??? parse and verify column header line.
         getLine();

         v = new int[nR*nL];
         if (!readTable(v, nR, nL)) return closeFile(false);
      }

      else {
         // ??? Error of some sort, garbage in the file.
      }

      //------------------------------------------------------------------------
      //--  Time to stash it                                                  --
      if (r && l && v) {
         if (!p) p = newPage(0);
         p->loadType(loadType);
         p->setSize(nR, nL, r, l, v);
         delete [] r; r = NULL;
         delete [] l; l = NULL;
         delete [] v; v = NULL;
         p = NULL;
      }
   }

   if (r) delete [] r;
   if (l) delete [] l;
   if (v) delete [] v;

   return closeFile(true);
}

//------------------------------------------------------------------------------
//--  Writer                                                                  --

bool vex::write(veTable &p)
{
   int nR = p.nRPMs();
   int nL = p.nLoads();
   fprintf(vexFile, "VE Table RPM Range              [%2d]\n", nR);
   int iR;
   for (iR = 0; iR < nR; iR++) fprintf(vexFile, "   [%3d] = %3d\n", iR, p.rpm(iR));
   fprintf(vexFile, "VE Table Load Range (%s)       [%2d]\n", p.loadType(), nL);
   int iL;
   for (iL = 0; iL < nL; iL++) fprintf(vexFile, "   [%3d] = %3d\n", iL, p.load(iL));
   fprintf(vexFile, "VE Table                        [%3d][%3d]\n", nR, nL);
   fprintf(vexFile, "          ");
   for (iR = 0; iR < nR; iR++) fprintf(vexFile, " [%3d]", iR);
   fprintf(vexFile, "\n");
   for (iL = 0; iL < nL; iL++) {
      fprintf(vexFile, "   [%3d] =", iL);
      for (iR = 0; iR < nR; iR++) {
         fprintf(vexFile, "%5d%s", p.ve(iR, iL), iR < nR-1 ? " " : "\n");
      }
   }
   return true;
}

bool vex::write(const char *fileName)
{
   vexFile = fopen(fileName, "w");
   if (vexFile == NULL) return false;

   fprintf(vexFile, "EVEME 1.0\n");

   const time_t t = ::time(NULL);
   struct tm *now = localtime(&t);
   strftime(Date, sizeof(Date), "%m-%d-%Y", now);
   strftime(Time, sizeof(Time), "%H:%M", now);

   fprintf(vexFile, "UserRev: %0.2f\n", UserRev);
   fprintf(vexFile, "UserComment: %s\n", UserComment);
   fprintf(vexFile, "Date: %s\n", Date);
   fprintf(vexFile, "Time: %s\n", Time);

   for (int iPage = 0; iPage < nPages; iPage++) {
      veTable *p = page(iPage);
      if (p) {
         fprintf(vexFile, "Page %d\n", iPage);
         if (!write(*p)) return closeFile(false);
      }
   }
   return closeFile(true);
}

//------------------------------------------------------------------------------
//--  Page management                                                         --

veTable *vex::page(int pageNo)
{
   if (pageNo <  0)      return NULL;
   if (pageNo >= nPages) return NULL;
   return pages[pageNo];
}

//------------------------------------------------------------------------------

veTable *vex::newPage(int pageNo)
{
   if (pageNo < 0) return false;
   if (pageNo >= nPages) {
      veTable **newPages = new veTable*[pageNo+1];
      if (newPages == NULL) return false;

      for (int i = 0; i < pageNo; i++) {
         newPages[i] = i >= nPages ? NULL : pages[i];
      }

      nPages = pageNo+1;
      if (pages) delete [] pages;
      pages = newPages;
   }

   return pages[pageNo] = new veTable;
}

//------------------------------------------------------------------------------

vex::vex()
 : pages (NULL),
   nPages(-1)
{
   userRev(1.0);
   userComment("");
   date("");
   time("");
}

//------------------------------------------------------------------------------

vex::~vex()
{
   if (pages) {
      for (int i = 0; i < nPages; i++) if (pages[i]) delete pages[i];
      delete [] pages;
   }
}

//==============================================================================

#ifdef VEX_TEST
int main(int argc, char *argv[])
{
   vex v;
   v.read("megasquirt.vex");

   veTable &p1 = *v.page(0);
   for (int r = 0; r < p1.nRPMs(); r++) p1.ve(2*r, r, 2);

   v.write("boink.vex");
   exit(0);
   return 0;
}
#endif

