//*****************************************
//******** PC Configurator V1.00 **********
//*** (C) - 2001 B.Bowling/A. Grippo ******
//** All derivatives from this software ***
//*** are required to keep this header ****
//*****************************************

#include "stdafx.h"
#include "megatune.h"
#include "msDatabase.h"
#include "veconst.h"

static char *rcsId = "$Id$";

#ifdef _DEBUG
#  define new DEBUG_NEW
#  undef THIS_FILE
   static char THIS_FILE[] = __FILE__;
#endif

//------------------------------------------------------------------------------

int msDatabase::maprange[8] = {  30,   40,   50,   60,   70,   80,   90,  100 };
int msDatabase::rpmrange[8] = { 500, 1000, 1500, 2000, 2500, 3000, 3500, 5500 };

unsigned char msDatabase::adf[256] = { // Default table for GM sensor.
   100,  59,  63,  66,  68,  70,  71,  72,
    73,  74,  74,  75,  76,  76,  77,  77,
    78,  78,  79,  79,  80,  80,  80,  81,
    81,  81,  82,  82,  82,  82,  83,  83,
    83,  83,  84,  84,  84,  84,  85,  85,
    85,  85,  85,  86,  86,  86,  86,  86,
    87,  87,  87,  87,  87,  87,  88,  88,
    88,  88,  88,  88,  89,  89,  89,  89,
    89,  89,  89,  90,  90,  90,  90,  90,
    90,  90,  91,  91,  91,  91,  91,  91,
    91,  92,  92,  92,  92,  92,  92,  92,
    92,  93,  93,  93,  93,  93,  93,  93,
    93,  94,  94,  94,  94,  94,  94,  94,
    94,  94,  95,  95,  95,  95,  95,  95,
    95,  95,  96,  96,  96,  96,  96,  96,
    96,  96,  96,  97,  97,  97,  97,  97,
    97,  97,  97,  97,  98,  98,  98,  98,
    98,  98,  98,  98,  98,  99,  99,  99,
    99,  99,  99,  99,  99, 100, 100, 100,
   100, 100, 100, 100, 100, 101, 101, 101,
   101, 101, 101, 101, 101, 102, 102, 102,
   102, 102, 102, 102, 102, 103, 103, 103,
   103, 103, 103, 103, 104, 104, 104, 104,
   104, 104, 104, 105, 105, 105, 105, 105,
   105, 105, 106, 106, 106, 106, 106, 106,
   107, 107, 107, 107, 107, 108, 108, 108,
   108, 108, 109, 109, 109, 109, 109, 110,
   110, 110, 110, 111, 111, 111, 111, 112,
   112, 112, 112, 113, 113, 113, 114, 114,
   114, 115, 115, 116, 116, 116, 117, 117,
   118, 119, 119, 120, 121, 121, 122, 123,
   124, 126, 127, 129, 131, 135, 141, 100
};

unsigned char msDatabase::thf[256] = { // Default table for GM sensor.
   210, 255, 255, 255, 255, 255, 255, 255,
   255, 255, 255, 255, 255, 255, 255, 255,
   254, 250, 247, 243, 240, 237, 234, 231,
   229, 226, 224, 222, 219, 217, 215, 213,
   211, 209, 207, 206, 204, 202, 201, 199,
   198, 196, 195, 193, 192, 191, 189, 188,
   187, 185, 184, 183, 182, 181, 179, 178,
   177, 176, 175, 174, 173, 172, 171, 170,
   169, 168, 167, 166, 165, 164, 163, 162,
   161, 161, 160, 159, 158, 157, 156, 155,
   155, 154, 153, 152, 151, 151, 150, 149,
   148, 148, 147, 146, 145, 145, 144, 143,
   142, 142, 141, 140, 139, 139, 138, 137,
   137, 136, 135, 135, 134, 133, 133, 132,
   131, 131, 130, 129, 129, 128, 127, 127,
   126, 125, 125, 124, 123, 123, 122, 122,
   121, 120, 120, 119, 118, 118, 117, 116,
   116, 115, 115, 114, 113, 113, 112, 111,
   111, 110, 110, 109, 108, 108, 107, 106,
   106, 105, 105, 104, 103, 103, 102, 101,
   101, 100, 100,  99,  98,  98,  97,  96,
    96,  95,  94,  94,  93,  92,  92,  91,
    90,  90,  89,  88,  88,  87,  86,  86,
    85,  84,  83,  83,  82,  81,  81,  80,
    79,  78,  78,  77,  76,  75,  74,  74,
    73,  72,  71,  70,  70,  69,  68,  67,
    66,  65,  64,  63,  63,  62,  61,  60,
    59,  58,  57,  56,  55,  53,  52,  51,
    50,  49,  48,  46,  45,  44,  42,  41,
    40,  38,  36,  35,  33,  31,  30,  28,
    26,  23,  21,  19,  16,  13,  10,   7,
     3,   0,   0,   0,   0,   0,   0, 210
};

unsigned char msDatabase::kpaf[256] = { // Default table for MPX4115.
   100, 100,  11,  11,  12,  12,  13,  13,
    13,  14,  14,  15,  15,  16,  16,  16,
    17,  17,  18,  18,  19,  19,  19,  20,
    20,  21,  21,  22,  22,  22,  23,  23,
    24,  24,  25,  25,  25,  26,  26,  27,
    27,  28,  28,  28,  29,  29,  30,  30,
    31,  31,  31,  32,  32,  33,  33,  34,
    34,  34,  35,  35,  36,  36,  37,  37,
    37,  38,  38,  39,  39,  40,  40,  40,
    41,  41,  42,  42,  43,  43,  43,  44,
    44,  45,  45,  46,  46,  46,  47,  47,
    48,  48,  49,  49,  49,  50,  50,  51,
    51,  51,  52,  52,  53,  53,  54,  54,
    54,  55,  55,  56,  56,  57,  57,  57,
    58,  58,  59,  59,  60,  60,  60,  61,
    61,  62,  62,  63,  63,  63,  64,  64,
    65,  65,  66,  66,  66,  67,  67,  68,
    68,  69,  69,  69,  70,  70,  71,  71,
    72,  72,  72,  73,  73,  74,  74,  75,
    75,  75,  76,  76,  77,  77,  78,  78,
    78,  79,  79,  80,  80,  81,  81,  81,
    82,  82,  83,  83,  84,  84,  84,  85,
    85,  86,  86,  87,  87,  87,  88,  88,
    89,  89,  90,  90,  90,  91,  91,  92,
    92,  93,  93,  93,  94,  94,  95,  95,
    95,  96,  96,  97,  97,  98,  98,  98,
    99,  99, 100, 100, 101, 101, 101, 102,
   102, 103, 103, 104, 104, 104, 105, 105,
   106, 106, 107, 107, 107, 108, 108, 109,
   109, 110, 110, 110, 111, 111, 112, 112,
   113, 113, 113, 114, 114, 115, 115, 116,
   116, 116, 117, 117, 118, 118, 100, 100
};

unsigned char msDatabase::barf[256] = { // Default table for MPX4115.
   100, 100, 141, 141, 141, 141, 140, 140,
   140, 140, 140, 139, 139, 139, 139, 139,
   139, 139, 138, 138, 138, 138, 138, 137,
   137, 137, 137, 136, 136, 136, 136, 136,
   135, 135, 135, 135, 135, 134, 134, 134,
   134, 133, 133, 133, 133, 133, 132, 132,
   132, 132, 132, 131, 131, 131, 131, 131,
   131, 131, 130, 130, 130, 130, 129, 129,
   129, 129, 129, 128, 128, 128, 128, 128,
   127, 127, 127, 127, 126, 126, 126, 126,
   126, 125, 125, 125, 125, 125, 124, 124,
   124, 124, 123, 123, 123, 123, 123, 123,
   123, 123, 122, 122, 122, 122, 121, 121,
   121, 121, 121, 120, 120, 120, 120, 120,
   119, 119, 119, 119, 118, 118, 118, 118,
   118, 117, 117, 117, 117, 117, 116, 116,
   116, 116, 115, 115, 115, 115, 115, 115,
   115, 114, 114, 114, 114, 114, 113, 113,
   113, 113, 113, 112, 112, 112, 112, 111,
   111, 111, 111, 111, 110, 110, 110, 110,
   110, 109, 109, 109, 109, 108, 108, 108,
   108, 108, 107, 107, 107, 107, 107, 107,
   107, 106, 106, 106, 106, 106, 105, 105,
   105, 105, 104, 104, 104, 104, 104, 103,
   103, 103, 103, 103, 102, 102, 102, 102,
   102, 101, 101, 101, 101, 100, 100, 100,
   100, 100, 100, 100,  99,  99,  99,  99,
    99,  98,  98,  98,  98,  98,  97,  97,
    97,  97,  96,  96,  96,  96,  96,  95,
    95,  95,  95,  95,  94,  94,  94,  94,
    93,  93,  93,  93,  93,  92,  92,  92,
    92,  92,  92,  92,  91,  91, 100, 100
};

msDatabase::thermType msDatabase::therm = msDatabase::Fahrenheit;

double msDatabase::tempFromDb(double t)
{
   if (therm == Celsius) {
      t = (t-32.0) * 5.0/9.0;
   }
   return t;
}

double msDatabase::dbFromTemp(double t)
{
   if (therm == Celsius) {
      t = t * 9.0/5.0 + 32.0;
   }
   return t;
}

void msDatabase::fixThermoLabel(CStatic &l, char *fmt, double value)
{
   char t = therm == Fahrenheit ? 'F' : 'C';
   char b[128];
   if (fmt)
      sprintf(b, fmt, tempFromDb(value));
   else
      l.GetWindowText(b,128);
   int n = strlen(b)-1;
   if (b[n] == ')') n--;
   b[n] = t;
   l.SetWindowText(b);
}

//------------------------------------------------------------------------------
// msDatabase dialog

#include <direct.h>

static unsigned char iDwwu[]   = { 120, 120, 115, 115, 115, 110, 105, 100, 100, 100 };
static unsigned char iDtpsaq[] = { 5, 20, 40, 77 };

msDatabase::msDatabase(CWnd* pParent /*=NULL*/)
 : CDialog        (msDatabase::IDD, pParent),
   controllerVersion(-1.0),
   _dualTable     (false),
   controllerResetCount(0),
   _recording     (false),
   commPortNumber (  1),
   _logging       (false),
   reqFuel        (0.0),
   rpmk           (  0),
   timerInterval  (200),
   nCylinders     (  8),
   mapType        (  0),
   portType       (  0),
   engType        (  0),
   afr            (14.7),
   cid            (0.0),
   injectorFlow   (0.0),
   _pageNo        (0),
   _changed       (false),
   _loaded        (false),
   _burned        (true) // Until we send something out.
{
   _getcwd(cwd, sizeof(cwd));
   for (int i = 0; i < 64; i++) _const[Dve+i]    = 1;
   for (    i = 0; i < 10; i++) _const[Dwwu+i]   = iDwwu[i];
   for (    i = 0; i <  4; i++) _const[Dtpsaq+i] = iDtpsaq[i];

   _const[Drpmk]         = 0x05;
   _const[Drpmk+1]       = 0xDC;

   _const[Dprimep]       =  15;
   _const[Dcwu]          = 100;
   _const[Dcwh]          =  25;
   _const[Dawev]         =  20;
   _const[Dawc]          = 250;
   _const[Dtpsacold]     =   2;
   _const[Dtpsthresh]    =   6;
   _const[Dtpsaclkcmp]   =  10;
   _const[Dtpsdq]        =  20;
   _const[Degotemp]      = 200;
   _const[Degocountcmp]  =  32;
   _const[Degodelta]     =   1;
   _const[Degolimit]     =  10;
   _const[Drpmoxlimit]   =  12;
   _const[Degoswitchv]   =  26; // Raw ADC for 0.50v

   _const[Dreq_fuel]     = 155;
   _const[Ddivider]      =   4;
   _const[Dalternate]    =   1;
   _const[Dinjopen]      =  10;
   _const[Dinjocfuel]    =   0;
   _const[Dinjpwm]       =  30;
   _const[Dinjpwmt]      = 255;
   _const[Dbattfac]      =  12;
   _const[Dconfig11]     = 224;
   _const[Dconfig12]     =   0;
   _const[Dconfig13]     =   0;
   _const[Dfastidle]     = 185;

   for (i = 0; i < 8; i++) {
      _const[Drpmrangeve+i] = static_cast<unsigned char>(rpmrange[i] / 100);
      _const[Dkparangeve+i] = static_cast<unsigned char>(maprange[i]);
   }

   memcpy(_const+128, _const, 128);

   //{{AFX_DATA_INIT(msDatabase)
	//}}AFX_DATA_INIT

   readConfig();
   openCommPort();
   getConst(); // Attempt to grab data from controller.
}

msDatabase::~msDatabase()
{
   if (_recording) {
      _recording = false; // Do this before closing the file in case the timer fires.
      fclose(_recordfile);
   }
}

//------------------------------------------------------------------------------

void msDatabase::DoDataExchange(CDataExchange* pDX)
{
   CDialog::DoDataExchange(pDX);
   //{{AFX_DATA_MAP(msDatabase)
	//}}AFX_DATA_MAP
}

//------------------------------------------------------------------------------

BEGIN_MESSAGE_MAP(msDatabase, CDialog)
   //{{AFX_MSG_MAP(msDatabase)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

//------------------------------------------------------------------------------
// msDatabase message handlers

BEGIN_EVENTSINK_MAP(msDatabase, CDialog)
   //{{AFX_EVENTSINK_MAP(msDatabase)
	//}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()

//------------------------------------------------------------------------------

FILE *cfgOpen(const char *fileName, const char *mode)
{
    // First, look mtCfg dir, then look "here."
   char configDir[128];
   sprintf(configDir, "mtCfg/%s", fileName);
   FILE *file = fopen(configDir, mode);
   if (file == NULL) file = fopen(fileName, mode);
   return file;
}

//------------------------------------------------------------------------------
   // There are three input messages handled here.
   //
   //    Signature (response to the "S" request);
   //    VE/constants tables (response to the "V" request);
   //    Run-time variables (response to the "A" request).
   //
   // The signature is arbitrary length, you must check it
   // on input.  The VE/constants table is 128 bytes long and
   // the run-time variables are 22 bytes.

//------------------------------------------------------------------------------

bool msDatabase::number(char *s, long &n)
{
   n = 0;

   // Prefixes: % = binary, $ = hexadecimal, ! = decimal.
   // Suffixes: Q = binary, H = hexadecimal, T = decimal.

   int  base = 10;
   char ch   = s[0];

   switch (ch) {
      case '%': base =  2; s[0] = ' '; break;
      case '$': base = 16; s[0] = ' '; break;
      case '!': base = 10; s[0] = ' '; break;

      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':
      case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
      case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
         ch = s[strlen(s)-1];
         switch (ch) {
            case 'Q': case 'q': base =  2; break;
            case 'H': case 'h': base = 16; break;
            case 'T': case 't': base = 10; break;

            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
            case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
            case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
               // Everthing is ok so far.
               break;

            default:
               return false;
               break;
         }
         break;
      default:
         return false;
         break;
   }

   n = strtol(s, NULL, base);
   return true;
}

bool msDatabase::number(char *s, BYTE &n)
{
   long nn;
   if (!number(s, nn))
      return false;
   else {
      if (nn > 255 || nn < 0) return false;
      n = static_cast<unsigned char>(nn);
   }
   return true;
}

bool msDatabase::charIn(char c, char *set)
{
   return strchr(set, c) != NULL;
}

//------------------------------------------------------------------------------

#define WHITESPACE " \t"

bool msDatabase::readTable(char *fileName, BYTE *values, bool msgDisp)
{
   int    nValues = 0;
   FILE   *tableFile = cfgOpen(fileName, "r");
   bool   ok = true;

   if (tableFile == NULL) {
      char msg[128];
      sprintf(msg,
         "Configuration file %s not found.\n"
         "Using default values for internal table.", fileName);
      if (msgDisp) MessageBox(msg, "MegaTune");
      ok = false;
   }
   else {
      char lineBuffer[512];

      //------------------------------------------------------------------------
      //--  Parses lines of the form:                                         --
      //--                                                                    --
      //--    ^<whitespace>DB<whitespace><number><any old junk>$              --
      //--                                                                    --
      //--  where character case is insignificant, and the form of numbers    --
      //--  is derived from P&E 6808 assembly language.                       --

      while (fgets(lineBuffer, sizeof(lineBuffer)-1, tableFile)) {
         char *s = lineBuffer;

         if (!charIn(*s, WHITESPACE)) continue;
         while (charIn(*s, WHITESPACE)) s++;        // Skip leading white space.
         if (!charIn(*s, "dD")) continue; else s++;
         if (!charIn(*s, "bB")) continue; else s++;
         if (!charIn(*s, WHITESPACE)) continue;
         while (charIn(*s, WHITESPACE)) s++;

         char *last = s;               // Null terminate the string of interest.
         while (charIn(*last, "0123456789$%!QHTqht")) last++;
         *last = '\0';

         if (nValues >= 256) {
            char msg[128];
            sprintf(msg,
               "Too many values (more than 256) found in %s.\n"
               "Extra values will be ignored.", fileName);
            MessageBox(msg);
            ok = false;
            break;
         }
         if (!number(s, values[nValues++])) {
            char msg[128];
            sprintf(msg,
               "\"%s\" is not a number in %s.\n"
               "Internal table will be wrong.", s, fileName);
            MessageBox(msg);
            ok = false;
            break;
         }
      }

      fclose(tableFile);

      if (nValues < 256) {
         char msg[128];
         sprintf(msg,
            "Too few values (fewer than 256) found in %s.\n"
            "Internal table will be wrong.", fileName);
         MessageBox(msg);
         ok = false;
      }
   }

   return ok;
}

void msDatabase::readConfig()
{
   FILE *msConfig = cfgOpen("megatune.cfg", "r");
   if (msConfig != NULL) {
      char cbuf[100];
      if (fgets(cbuf, 100, msConfig) != NULL) {
         if (cbuf[3] == '1') commPortNumber = 1;
         if (cbuf[3] == '2') commPortNumber = 2;
         if (cbuf[3] == '3') commPortNumber = 3;
         if (cbuf[3] == '4') commPortNumber = 4;
      }

      if (fgets(cbuf, 100, msConfig) != NULL) {
         int igtemp;
         sscanf(cbuf, "%d", &igtemp);
         timerInterval = igtemp;
      }
      fclose(msConfig);
   }

   readTable("airdenfactor.inc", adf,  true);
   readTable("thermfactor.inc",  thf,  true);
   readTable("kpafactor.inc",    kpaf, true);
   maxmap = kpaf[0];
   for (int i = 1; i < 256; i++) if (kpaf[i] > maxmap) maxmap = kpaf[i];
   if (!readTable("throttlefactor.inc", tpsf, false)) {
      for (int i = 0; i < 256; i++) tpsf[i] = static_cast<unsigned char>(i); // static_cast<unsigned char>(i * 100.0/255.0);
   }
}

void msDatabase::writeConfig()
{
   closeCommPort(); // gack, put this in setCommPort
   openCommPort();

   FILE *msConfig = cfgOpen("megatune.cfg", "w");
   if (msConfig == NULL)
      MessageBox("Configuration file megatune.cfg can't be written.\nDirectory protection error?");
   else {
      fprintf(msConfig, "COM%d\n", commPortNumber);
      fprintf(msConfig, "%d\n", timerInterval);
      fclose(msConfig);
   }
}

//------------------------------------------------------------------------------

static char BASED_CODE szXlsFilter[] = "Excel Spreadsheet (*.xls)|*.xls|All Files (*.*)|*.*";

void msDatabase::setRecording(bool rec)
{
   if (!rec) {
      _recording = false; // Make sure to turn it off first.
      if (_recordfile) {
         fclose(_recordfile);
         _recordfile = NULL;
      }
   }
   else if (rec) {
      if (_recording) {
         fclose(_recordfile);
         _recordfile = NULL;
         _recording  = false;
      }

      CFileDialog dlgr(FALSE, "xls", "datalog.xls", OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, szXlsFilter);
      char cwd[_MAX_PATH];
      _getcwd(cwd, sizeof(cwd));
      dlgr.m_ofn.lpstrInitialDir = cwd;

      if (dlgr.DoModal() == IDOK) {
         TRY {
            _recordfile = fopen(dlgr.GetPathName(), "w");

            fprintf(_recordfile, "Seconds\tRPM\tMAP\tO2\tEngineBit\tGego\tGair\tGwarm\tGbaro\tGve\tGammae\tPW\n");
            _recording = true;
         }
         CATCH(CFileException, e) {
            char message[200];
            sprintf(message, "File \"%s\" can't be opened", dlgr.GetPathName());
            MessageBox(message);
            _recording = false;
         }
         END_CATCH
      }
   }
}

//------------------------------------------------------------------------------

static char BASED_CODE szMsqFilter[] = "MegaSquirt Settings Files (*.msq)|*.msq|All Files (*.*)|*.*";

// 1.0.1 is original release through 2.00
// 2.0.0 is dual table files.

#define Major 2 // File version, not MegaTune.
#define Minor 0
#define Rev   0

struct versionInfo {
   char       desc[10];          // 16 bytes, tag plus numbers.
   short int  major;
   short int  minor;
   short int  rev;

   versionInfo() : major(Major), minor(Minor), rev(Rev) {
      assert(sizeof(versionInfo) == 16);
      strcpy(desc, "#MegaTune"); // That's 10 including the null terminator.
   }
   versionInfo(short int M, short int m, short int r)
    : major(M),
      minor(m),
      rev  (r)
   {
      assert(sizeof(versionInfo) == 16);
      strcpy(desc, "#MegaTune"); // That's 10 including the null terminator.
   }

   operator ==(versionInfo &rhs)
   {
      return
         major == rhs.major &&
         minor == rhs.minor &&
         rev   == rhs.rev   &&
         strcmp(desc, "#MegaTune") == 0;
   }
};

static versionInfo v101(1, 0, 1);
static versionInfo v200(2, 0, 0);

//------------------------------------------------------------------------------

void msDatabase::write()
{
      TRY {
         CFile msqFile;
         msqFile.Open(settingsFile, CFile::modeCreate | CFile::modeWrite);
            versionInfo v;
            msqFile.Write(&v,            sizeof(versionInfo));
            msqFile.Write(_const,        sizeof(_const));
            msqFile.Write(&cid,          sizeof(cid));
            msqFile.Write(&injectorFlow, sizeof(injectorFlow));
            msqFile.Write(&afr,          sizeof(afr));
         msqFile.Close();
      }
      CATCH(CFileException, e) {
         char message[128];
         sprintf(message, "File \"%s\" can't be opened", settingsFile);
         MessageBox(message);
      }
      END_CATCH
}

void msDatabase::saveAs()
{
   CFileDialog dlgr(FALSE, "msq", "megasquirt.msq", OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, szMsqFilter);
   dlgr.m_ofn.lpstrInitialDir = cwd;
   if (dlgr.DoModal() == IDOK) {
      settingsFile = dlgr.GetPathName();
      write();
   }
}

void msDatabase::save()
{
   if (settingsFile == "")
      saveAs();
   else {
      write();
   }
}

//------------------------------------------------------------------------------

void msDatabase::open()
{
   CFileDialog dlgr(TRUE, "msq", NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, szMsqFilter);
   dlgr.m_ofn.lpstrInitialDir = cwd;
   if (dlgr.DoModal() == IDOK) {
      TRY {
         CFile msqFile;
         settingsFile = dlgr.GetPathName();
         msqFile.Open(settingsFile, CFile::modeRead);
         versionInfo v, current;
         msqFile.Read(&v, sizeof(versionInfo));
         int size = 0;
         if (v == v101) size = 129;
         if (v == v200) size = 257;
         if (size > 0) {
            msqFile.Read(_const,        size);
            msqFile.Read(&cid,          sizeof(cid));
            msqFile.Read(&injectorFlow, sizeof(injectorFlow));
            msqFile.Read(&afr,          sizeof(afr));
            nCylinders = ncyl_mask_bits(Const(Dconfig11))+1;
            nSquirts   = int(0.00001 + double(nCylinders) / double(Const(Ddivider)));
            _loaded  = true;
            _changed = false;
         }
         else {
            char message[128];
            sprintf(message, "File has wrong version, expected %d.%d.%d and found %d.%d.%d", current.major, current.minor, current.rev, v.major, v.minor, v.rev);
            MessageBox(message);
            _loaded  = false;
            _changed = true;
         }
         msqFile.Close();
      }
      CATCH(CFileException, e) {
         char message[128];
         sprintf(message, "File \"%s\" can't be opened", dlgr.GetPathName());
         MessageBox(message);
      }
      END_CATCH
   }
}

//------------------------------------------------------------------------------

int msDatabase::alphaN()
{
   return alphaN_from_bits(Const(Dconfig13));
}

void msDatabase::alphaN(int setting)
{
   Const(Dconfig13, static_cast<unsigned char>((Const(Dconfig13) & ~ALPHAN_MASK_BITS) | setting << 2));
}

int msDatabase::egoType()
{
   return wbego_from_bits(Const(Dconfig13));
}

void msDatabase::egoType(int value)
{
   Const(Dconfig13, static_cast<unsigned char>((Const(Dconfig13) & ~O2SENSOR_MASK_BITS) | value << 1));
}

int msDatabase::baroCorr()
{
   return baroc_from_bits(Const(Dconfig13));
}

void msDatabase::baroCorr(int value)
{
   Const(Dconfig13, static_cast<unsigned char>((Const(Dconfig13) & ~BAROC_MASK_BITS) | value << 3));
}

//------------------------------------------------------------------------------

int  msDatabase::pageNo()
{
   return _pageNo;
}

int msDatabase::setPageNo(int pageNo)
{
   if (pageNo > 1)  pageNo = 1;
   if (pageNo < 0)  pageNo = 0;
   if (!_dualTable) pageNo = 0;
   return _pageNo = pageNo;
}

bool msDatabase::dualTable()
{
   return _dualTable;
}

//------------------------------------------------------------------------------

unsigned char msDatabase::Const   (int offset)         { return  _const[offset+_pageNo*128]; }
unsigned char msDatabase::Const   (int offset, char v) { return (_const[offset+_pageNo*128] = v); }
unsigned char msDatabase::ConstRaw(int offset)         { return  _const[offset]; }
unsigned char msDatabase::ConstRaw(int offset, char v) { return (_const[offset] = v); }

// need a putConst which writes the whole 128 bytes to ram.

void msDatabase::putConstByte(int offset)
{
   sendPage();
   BYTE outbuf[3] = { 'W', char(offset), Const(offset) };
   writeCommPort(outbuf, 3);
   _burned = false;
}

void msDatabase::putConstByte(int offset, int value, bool sendit)
{
   bool changed = Const(offset) != value;
   Const(offset, static_cast<unsigned char>(value));
   if (sendit) putConstByte(offset);
   _changed = _changed || changed;
}

void msDatabase::burnConst()
{
   unsigned char rBuf[Rget];
   if (!getRuntime(rBuf))
      MessageBox("Could not connect to controller.", "No Connection");
   else {
      if (vBatt < 7.0) {
         if (MessageBox("Controller voltage too low for reliable FLASH burning.\nTry anyhow?", "Burn Flash", MB_YESNO | MB_DEFBUTTON2 | MB_ICONQUESTION | MB_SYSTEMMODAL) == IDNO) return;
      }
      writeCommPort("B");
      _burned = true;
   }
}

//------------------------------------------------------------------------------

void msDatabase::getVersion()
{
   if (1||controllerVersion == -1.0) {
      writeCommPort("Q");
      BYTE q;
      if (!readCommPort(q))
         controllerVersion = 1.0;
      else
         controllerVersion = double(q) / 10.0;

      _dualTable = controllerVersion == 0.1;

      if (_dualTable) {
         controllerVersion = 2.0; // In all other regards, this is 2.0.
      }
#if 0
      writeCommPort("S");
      BYTE nBytes = 0;
      if (readCommPort(nBytes)) {
         for (int i = 0; i < nBytes; i++) {
            if (!readCommPort(q)) break;
         }
      }
#endif
   }
}

//------------------------------------------------------------------------------

void msDatabase::sendPage(int pageNo)
{
   getVersion();
   if (!_dualTable) return;

   BYTE pagbuf[2] = { 'P', static_cast<BYTE>(pageNo >= 0 ? pageNo : _pageNo) };
   writeCommPort(pagbuf, 2);
}

//------------------------------------------------------------------------------

bool msDatabase::getMemory(BYTE pageNumber, BYTE buffer[256])
{
   getVersion();
   if (!_dualTable) return false;

   BYTE cmd[2] = { 'F', pageNumber };
   writeCommPort(cmd, 2);

   for (int i = 0; i < 256; i++) {
      if (!readCommPort(buffer[i])) return false;
   }

   return true;
}

//------------------------------------------------------------------------------

bool msDatabase::getConst(int pageNo)
{
   sendPage(pageNo);

   writeCommPort("V");
   int nBytes = dualTable() ? 128 : 125;
   int ofs = (pageNo >= 0 ? pageNo : _pageNo)*128;
   for (int i = 0; i < nBytes; i++) {
      if (!readCommPort(_const[i+ofs])) {
         return false;
      }
   }

   nCylinders = ncyl_mask_bits(Const(Dconfig11))+1;
   nSquirts   = int(0.00001 + double(nCylinders) / double(Const(Ddivider)));

   return true;
}

//------------------------------------------------------------------------------

bool msDatabase::getRuntime(unsigned char rBuf[Rget])
{
   controllerReset = false;
   static int previousSecl = 255;

   writeCommPort("A");
   for (int i = 0; i < Rget; i++) {
      if (!readCommPort(rBuf[i])) {
         while (i < Rget) rBuf[i++] = 0;
         return false;
      }
   }

   unsigned char secl = rBuf[Rsecl];
   if (secl != previousSecl) {
      if (secl == 0 && previousSecl != 255) {
         // An unexpected reset of controller has occurred.
         //MessageBeep(MB_ICONEXCLAMATION);
         Beep(2000,100);
         Beep(1000,100);
         Beep(2000,100);
         controllerReset = true;
         controllerResetCount++;
      }
      previousSecl = secl;
   }

   int alternating1 = Const(Dalternate) ? 2 : 1;
   int alternating2 = _dualTable ? (_const[Dalternate+128] ? 2 : 1) : alternating1;
   cycleTime = 600.0 / rBuf[Rrpm]; // ms per cycle, assume 2 stroke.
   if (stroke_from_bits(Const(Dconfig11)) == 0) cycleTime *= 2.0; // For 4 stroke.
   pulseWidth1 = double(rBuf[Rpw] / 10.0);
   dutyCycle1 = 100.0 * nSquirts * pulseWidth1 / cycleTime;
   pulseWidth2 = _dualTable ? double(rBuf[Rpw2] / 10.0) : pulseWidth1;
   dutyCycle2 = 100.0 * nSquirts * pulseWidth2 / cycleTime;

   vBatt = rBuf[Rbatt]*30.0 / 255.0;

   throttle = tpsf[rBuf[Rtps]];
   map      = kpaf[rBuf[Rmap]];
   mat      = tempFromDb(thf[rBuf[Rmat]]-40); // WRONG TABLE! THF is really just for coolant, so this could be wrong if the sensors are different.
   coolant  = tempFromDb(thf[rBuf[Rclt]]-40);

   // Datalog record (if enabled)
   if (_recording) {
      fprintf(_recordfile, "%d\t%d\t%d\t%.3f\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%.1f\n",
              rBuf[Rsecl],
              rBuf[Rrpm] * 100,
              map,
              rBuf[Rego] * 0.0195312,
              rBuf[Rengine],
              rBuf[Regocorr],
              rBuf[Raircorr],
              rBuf[Rwarmcorr],
              rBuf[Rbarocorr],
              rBuf[Rvecurr],
              rBuf[Rgammae],
              rBuf[Rpw]/10.0);
      fflush(_recordfile);
   }

   return true;
}

//------------------------------------------------------------------------------

bool msDatabase::toggleCommLogging()
{
   return _logging = !_logging;
}

//------------------------------------------------------------------------------

void msDatabase::log(logType op, bool ok, BYTE *buf, int len)
{
   if (!_logging) return;

   static logType  lastOp = rd;
   static char    *type[2] = { "RCV", "SND" };
   static int      pos = 0;

   static FILE *logFile = NULL;
   if (logFile == NULL) logFile = fopen("comm.log", "w");

   if (logFile != NULL) {
      if (op == wr || op != lastOp) {
         pos = 0;
         fprintf(logFile, "\n%c %s", ok?' ':'X', type[op]);
      }
      lastOp = op;

      for (int i = 0; i < len; i++) {
         if (pos > 0 && pos % 16 == 0) fprintf(logFile, "\n     ");
         if (pos == 0 && op == wr)
            fprintf(logFile, " %c ", buf[0]);
         else
            fprintf(logFile, " %02x", buf[i]);
         pos++;
      }
      fflush(logFile);
   }
}

//------------------------------------------------------------------------------
//--  Lifted substantially from Tom Dolsky's hw2sys routines.                 --

bool msDatabase::openCommPort()
{
   char pPortName[6];
   sprintf(pPortName, "com%d", commPortNumber);
   hPortHandle = CreateFile(pPortName,
                    GENERIC_READ | GENERIC_WRITE,
                    0,
                    NULL,
                    OPEN_EXISTING,
                    FILE_ATTRIBUTE_NORMAL,
                    NULL);
   if (hPortHandle == INVALID_HANDLE_VALUE) return false;

   DCB dcb;
   if (!GetCommState(hPortHandle, &dcb)) return false;
   dcb.BaudRate = 9600;
   dcb.ByteSize = 8;
   dcb.Parity   = NOPARITY;
   dcb.StopBits = ONESTOPBIT;
   if (!SetCommState(hPortHandle, &dcb)) return false;

   COMMTIMEOUTS timeout;
   if (!GetCommTimeouts(hPortHandle, &timeout)) return false;
   timeout.ReadIntervalTimeout         =   0; // ms between chars on read.
   timeout.ReadTotalTimeoutMultiplier  =   0;
   timeout.ReadTotalTimeoutConstant    = 250;
   timeout.WriteTotalTimeoutMultiplier =   0;
   timeout.WriteTotalTimeoutConstant   =   0;
   if (!SetCommTimeouts(hPortHandle, &timeout)) return false;

   return true;
}

//------------------------------------------------------------------------------

void msDatabase::closeCommPort()
{
   if (hPortHandle != NULL) {
      CloseHandle(hPortHandle);
      hPortHandle = NULL;
   }
}

//------------------------------------------------------------------------------
// readCommPort - Read char from from the COM port. Blocks if no char available.

bool msDatabase::readCommPort(unsigned char &pChar)
{
   DWORD dwActualRead;
   bool bOK = 0 != ReadFile(hPortHandle, &pChar, 1, &dwActualRead, NULL);
   if (dwActualRead != 1) bOK = false;
   log(rd, bOK, &pChar, 1);
   return bOK;
}

//------------------------------------------------------------------------------

bool msDatabase::writeCommPort(BYTE *pBuf, DWORD dwCount)
{
   DWORD dwBytesWritten;
   bool  bOK = 0 != WriteFile(hPortHandle, pBuf, dwCount, &dwBytesWritten, NULL);
   if (dwBytesWritten != dwCount) bOK = false;
   log(wr, bOK, pBuf, dwCount);
   return bOK;
}

bool msDatabase::writeCommPort(char *pBuf)
{
   return writeCommPort(reinterpret_cast<BYTE *>(pBuf), strlen(pBuf));
}

//------------------------------------------------------------------------------

typedef struct {
   char *id;
   int   ofs;
   int   sz;
   char dbox;
   char *name;
} dmpDescriptor;

dmpDescriptor dmpTable[] = {
   { "Dprimep",      119,   1,  'E',     "Priming Pulse Duration" },
   { "Dcwu",          64,   1,  'E',     "Cold Cranking Pulsewidth" },
   { "Dcwh",          65,   1,  'E',     "Hot Cranking Pulsewidth" },
   { "Dawev",         66,   1,  'E',     "Afterstart Enrichment Percent" },
   { "Dawc",          67,   1,  'E',     "Afterstart Enrichment Cycles" },
   { "Dwwu",          68,  10,  'E',     "Warmup Enrichment Values" },
   { "Dtpsaq",        78,   4,  'E',     "Acceleration Enrichment Values" },
   { "Dtpsacold",     82,   1,  'E',     "Cold Acceleration Enrichment" },
   { "Dtpsthresh",    83,   1,  'E',     "TPSdot Threshold" },
   { "Dtpsaclkcmp",   84,   1,  'E',     "Acceleration Pulse Duration" },
   { "Dtpsdq",        85,   1,  'E',     "Deceleration Fuel Cut" },
   { "Degotemp",      86,   1,  'E',     "EGO Activation Temperature" },
   { "Degocountcmp",  87,   1,  'E',     "EGO Ignition Event Count" },
   { "Degodelta",     88,   1,  'E',     "EGO Step" },
   { "Degolimit",     89,   1,  'E',     "EGO +/- Limit" },
   { "Drpmoxlimit",  120,   1,  'E',     "EGO Closed-loop RPM Limit" },
   { "Degoswitchv",  122,   1,  'E',     "EGO Switch Voltage" },
   // 0-16 above

   { "Dreq_fuel",     90,   1,  'C',     "Required Fuel" },
   { "Ddivider",      91,   1,  'C',     "Injections Per Cycle" },
   { "Dalternate",    92,   1,  'C',     "Injector Staging" },
   { "Dinjopen",      93,   1,  'C',     "Injector Opening Time" },
   { "Dinjocfuel",    94,   1,  'C',     "Injector Open/Close Fuel" },
   { "Dinjpwm",       95,   1,  'C',     "Injector Current Limit" },
   { "Dinjpwmt",      96,   1,  'C',     "Injector PWM Time Threshold" },
   { "Dbattfac",      97,   1,  'C',     "Injector Opening Battery Factor" },
   { "Drpmk",         98,   2,  'C',     "RPM Constant" },
   { "Dconfig11",    116,   1,  'C',     "Configuration 11" },
   { "Dconfig12",    117,   1,  'C',     "Configuration 12" },
   { "Dconfig13",    118,   1,  'C',     "Configuration 13" },
   { "Dfastidle",    121,   1,  'C',     "Fast Idle Activation Temp" },
   // 17-29 above

   { "Drpmrangeve",  100,   8,  'V',     "VE Table RPM Range" },
   { "Dkparangeve",  108,   8,  'V',     "VE Table MAP Range" },
   { "Dve",            0,  64,  'V',     "VE Table" },
   // 30-32 above

   { "Unused",       123,   5,  '-',     "Unused data" }
};

#define dmpTableSize (sizeof(dmpTable)/sizeof(dmpDescriptor))

void msDatabase::dumpOne(FILE *dmp, int idx)
{
   dmpDescriptor *d = dmpTable+idx;
   if (d->sz == 1) {
      fprintf(dmp, "%-31s %-12s", d->name, d->id);
      fprintf(dmp, ": %3d = $%02x\n", Const(d->ofs), Const(d->ofs));
      switch (d->ofs) {
         case Dconfig11:
         {
            int c11 = Const(Dconfig11);
            fprintf(dmp, "   MAPType  = %d\n", map_from_bits(c11));
            fprintf(dmp, "   Stroke   = %d\n", stroke_from_bits(c11));
            fprintf(dmp, "   InjType  = %d\n", injectiontype_mask_from_bits(c11));
            fprintf(dmp, "   nCylbits = %d (=%d cylinders)\n", ncyl_mask_bits(c11), ncyl_mask_bits(c11)+1);
            break;
         }

         case Dconfig12:
         {
            int c12 = Const(Dconfig12);
            fprintf(dmp, "   CTS      = %d\n", cool_from_bits(c12));
            fprintf(dmp, "   MAT      = %d\n", mat_from_bits(c12));
            fprintf(dmp, "   nInj     = %d\n", ninject_from_bits(c12));
            break;
         }

         case Dconfig13:
         {
            int c13 = Const(Dconfig13);
            fprintf(dmp, "   Oddfire  = %d\n", oddfire_from_bits(c13));
            fprintf(dmp, "   WB EGO   = %d\n", wbego_from_bits(c13));
            fprintf(dmp, "   alpha-N  = %d\n", alphaN_from_bits(c13));
            fprintf(dmp, "   barocorr = %d\n", baroc_from_bits(c13));
            break;
         }
      }
   }
   else if (d->sz < 16) {
      fprintf(dmp, "%-31s [%2d] %-12s", d->name, d->sz, d->id);
      fprintf(dmp, "\n");
      for (int i = 0; i < d->sz; i++) {
         fprintf(dmp, "   [%3d] = %3d = $%02x\n", i, Const(d->ofs+i), Const(d->ofs+i));
      }
   }
   else { // Assume it's an 8x8 table.
      fprintf(dmp, "%-31s %-12s", d->name, d->id);
      fprintf(dmp, "\n          ");
      for (int c = 0; c < 8; c++) {
         fprintf(dmp, " [%3d]", c);
      }
      fprintf(dmp, "\n");
      for (int r = 0; r < 8; r++) {
         fprintf(dmp, "   [%3d] =", r);
         for (c = 0; c < 8; c++) {
            fprintf(dmp, "  %3d ", Const(d->ofs+r*8+c), Const(d->ofs+r*8+c));
         }
         fprintf(dmp, "\n");
      }
   }
}

void msDatabase::dump()
{
   FILE *dmp = fopen("MegaTune.dmp", "a");
   if (dmp == NULL)
      MessageBox("Couldn't write to MegaTune.dmp dump file.");
   else {
      const time_t t = time(NULL);
      fprintf(dmp, "MegaTune Dump %s", ctime(&t));
      fprintf(dmp, "Windows %d.%d %d.%d\n\n", _winmajor, _winminor, _osver, _winver);

      int savePage = _pageNo;
      for (_pageNo = 0; _pageNo < (_dualTable ? 2 : 1); _pageNo++) {
         fprintf(dmp, "Page %d\n", _pageNo);
         for (int i = 0; i < dmpTableSize; i++) {
            dumpOne(dmp, i);
         }
      }
      _pageNo = savePage;

      fprintf(dmp, "\n");
      fclose(dmp);
   }
}

//------------------------------------------------------------------------------

#include "vex.h"

static char BASED_CODE veMsqFilter[] = "VE Exchange (*.vex)|*.vex|All Files (*.*)|*.*";

void msDatabase::veExport()
{
   CFileDialog dlgr(FALSE, "vex", "megasquirt.vex", OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, veMsqFilter);
   dlgr.m_ofn.lpstrInitialDir = cwd;
   if (dlgr.DoModal() == IDOK) {
      CString fName = dlgr.GetPathName();
      FILE *exp = fopen(fName, "w");
      if (exp == NULL)
         MessageBox("Couldn't export "+fName+" VE Exchange file.", "VE Export");
      else {
         fclose(exp);

         vex v;
         int savePage = _pageNo;
         for (_pageNo = 0; _pageNo < (_dualTable ? 2 : 1); _pageNo++) {
            veTable *p = v.newPage(_pageNo);
            int rpm[8];
            int lod[8];
            int ve[64];
            for (int i = 0; i < 8; i++) {
               rpm[i] = Const(Drpmrangeve+i);
               lod[i] = Const(Dkparangeve+i);
            }
            for (i = 0; i < 64; i++) {
               ve[i] = Const(Dve+i);
            }

            p->setSize(8, 8, rpm, lod, ve);
         }
         _pageNo = savePage;
         v.write(LPCTSTR(fName));
      }
   }
}

//------------------------------------------------------------------------------

void msDatabase::veImport()
{
   CFileDialog dlgr(TRUE, "vex", NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, veMsqFilter);
   dlgr.m_ofn.lpstrInitialDir = cwd;
   if (dlgr.DoModal() == IDOK) {
      CString fName = dlgr.GetPathName();
      vex v;
      if (!v.read(LPCTSTR(fName))) MessageBox("Could not read "+fName, "VE Import");
      int savePage = _pageNo;
      for (_pageNo = 0; _pageNo < (_dualTable ? 2 : 1); _pageNo++) {
         veTable *p = v.page(_pageNo);
         if (!p) continue;

         if (p->nRPMs()  != 8) MessageBox("Table has wrong number of RPM bins", "VE Import");
         if (p->nLoads() != 8) MessageBox("Table has wrong number of load bins", "VE Import");

         for (int i = 0; i < 8; i++) {
            putConstByte(Drpmrangeve+i, p->rpm(i));
            putConstByte(Dkparangeve+i, p->load(i));
         }
         for (i = 0; i < 64; i++) {
            putConstByte(Dve+i, p->ve(i/8, i%8));
         }
      }
      _pageNo = savePage;
      v.write(LPCTSTR(fName));
   }
}

//------------------------------------------------------------------------------

bool msDatabase::loaded()    { return _loaded;    }
bool msDatabase::changed()   { return _changed;   }
bool msDatabase::burned()    { return _burned;    }

bool msDatabase::recording() { return _recording; }

//------------------------------------------------------------------------------

msDatabase mdb; // One and only one.

//------------------------------------------------------------------------------

