//*****************************************
//******** PC Configurator V1.00 **********
//*** (C) - 2001 B.Bowling/A. Grippo ******
//** All derivatives from this software ***
//**  are required to keep this header ****
//*****************************************

static char *rcsId() { return "$Id$"; }

#include "stdafx.h"
#include "megatune.h"
#include "msDatabase.h"
#include "repository.h"
#include "Dttune.h"
#include "veconst.h"

#ifdef _DEBUG
#  define new DEBUG_NEW
#  undef THIS_FILE
   static char THIS_FILE[] = __FILE__;
#endif

//------------------------------------------------------------------------------

extern msDatabase mdb;
extern repository rep;

//------------------------------------------------------------------------------

static long   wht       RGB(255, 255, 255);
static long   red       RGB(255,   0,   0);
static long   grn       RGB(  0, 255,   0);
static long   blu       RGB(  0,   0, 255);
static long   blk       RGB(  0,   0,   0);
static long   lgr       RGB(220, 220, 220);
static long   mgr       RGB(150, 150, 150);
static long   dgr       RGB(127, 127, 127);

static CPen   pwhite    (PS_SOLID, 0, wht);
static CPen   pspot     (PS_SOLID, 2, grn);
static CPen   pcursor   (PS_SOLID, 3, red);
static CBrush brushblk, brushred;


//------------------------------------------------------------------------------
// Dttune dialog

Dttune::Dttune(CWnd * pParent /*=NULL*/ )
 : CDialog(Dttune::IDD, pParent),
   currcurx  (0),
   currcury  (0),
   ivebin    (0)
{
   //{{AFX_DATA_INIT(Dttune)
	//}}AFX_DATA_INIT
}

//------------------------------------------------------------------------------

BEGIN_MESSAGE_MAP(Dttune, CDialog)
   //{{AFX_MSG_MAP(Dttune)
      ON_WM_KEYDOWN()
      ON_WM_PAINT()
      ON_WM_TIMER()
      ON_BN_CLICKED(IDC_BURNVE, OnBurn)
      ON_NOTIFY(UDN_DELTAPOS, IDC_TREQFUEL_SPIN, OnReqfuelSpin)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

//------------------------------------------------------------------------------

void Dttune::DoDataExchange(CDataExchange * pDX)
{
   CDialog::DoDataExchange(pDX);
   //{{AFX_DATA_MAP(Dttune)
      DDX_Control(pDX, IDC_TREQFUEL, m_tReqFuel);
	//}}AFX_DATA_MAP
}

//------------------------------------------------------------------------------

void Dttune::setMsg(char *newMsg, int line, long clr)
{
   static char oldMsg[5][100] = {"", "", "", "", ""};
   CDC *dc = GetDC();
      dc->SetBkMode(TRANSPARENT);
      dc->SetTextColor(blk);
      dc->TextOut(vepx1 + 10, wuy1 + 5 + (line*20), oldMsg[line]);
      dc->SetTextColor(clr);
      dc->TextOut(vepx1 + 10, wuy1 + 5 + (line*20), newMsg);
      strcpy(oldMsg[line], newMsg);
   ReleaseDC(dc);
}

//------------------------------------------------------------------------------

void Dttune::timer(int state)
{
   if (state == on) {
      if (SetTimer(1, mdb.timerInterval, NULL) == 0)
         MessageBox("ERROR: Cannot install timer.\nKill other useless Windows Apps.");
   }
   else {
      KillTimer(1);
      Sleep(mdb.timerInterval);
   }
}

//------------------------------------------------------------------------------
void logSize(CWnd *w, char *title);

BOOL Dttune::OnInitDialog()
{
   CDialog::OnInitDialog();

   logSize(this, "tune");

   if (!mdb.loaded()) mdb.getConst(); // If not here, grab from MS.
   for (int jk = 0; jk < 8; jk++) {
      mdb.rpmrange[jk] = mdb.Const(Drpmrangeve + jk) * 100;
      mdb.maprange[jk] = mdb.Const(Dkparangeve + jk);
   }

   double rpmborder = (mdb.rpmrange[7] - mdb.rpmrange[0]) * 0.07;
   minrpm = mdb.rpmrange[0] - rpmborder;
   maxrpm = mdb.rpmrange[7] + rpmborder;
   double mapborder = (mdb.maprange[7] - mdb.maprange[0]) * 0.05;
   minmap = mdb.maprange[0] - mapborder;
   maxmap = mdb.maprange[7] + mapborder;

   mdb.reqFuel = mdb.Const(Dreq_fuel) / 10.0;
   char s[10];
   sprintf(s, "%.1f", mdb.reqFuel);
   m_tReqFuel.SetWindowText(s);

   veBar = static_cast<CProgressCtrl *>(GetDlgItem(IDC_TTVE_BAR));

   CRect r;
   GetDlgItem(IDC_VEP)->GetWindowRect(&r);
   ScreenToClient(&r);
   vepx1 = r.left;
   vepx2 = r.right;
   vepy1 = r.top;
   vepy2 = r.bottom;

   GetDlgItem(IDC_WU)->GetWindowRect(&r);
   ScreenToClient(&r);
   wuy1 = r.top;
   wuy2 = r.bottom;

   sloperpm = double(vepx2 - vepx1) / (maxrpm - minrpm);
   slopemap = double(vepy2 - vepy1) / (maxmap - minmap);

   brushblk.DeleteObject();
   brushblk.CreateSolidBrush(blk);
   brushred.DeleteObject();
   brushred.CreateSolidBrush(red);

   veplotrect.SetRect(vepx1 - 12, vepy1 - 12, vepx2 + 12, vepy2 + 12);
   vewarmrect.SetRect(vepx1 - 12, wuy1  - 12, vepx2 + 12, wuy2  + 12);

   // Meter defaults
   meterrpm.SetRange(rep.lotRPM, rep.hitRPM);
   meterrpm.SetTitle("Engine RPM");
   meterrpm.SetUnit ("RPM");
   meterrpm.SetAlert(rep.rdtRPM > 0.0 ? rep.rdtRPM : rep.hitRPM);
   meterrpm.SetValueDecimals(0);
   meterrpm.SetRangeDecimals(0);

   if (mdb.alphaN() == 1) {
      metermap.SetRange(rep.lotTR, rep.hitTR);
      metermap.SetTitle("TPS");
      metermap.SetUnit("%");
      metermap.SetAlert(rep.rdtTR > 0.0 ? rep.rdtTR : rep.hitTR);
   }
   else {
      metermap.SetRange(rep.lotMAP, rep.hitMAP);
      metermap.SetTitle("Engine MAP");
      metermap.SetUnit("kPa");
      metermap.SetAlert(rep.rdtMAP > 0.0 ? rep.rdtMAP : rep.hitMAP);
   }
   metermap.SetValueDecimals(0);
   metermap.SetRangeDecimals(0);

   meterve.SetRange(rep.lotVEG, rep.hitVEG);
   meterve.SetTitle("VE Bucket");
   meterve.SetUnit("%");
   meterve.SetAlert(rep.rdtVEG > 0.0 ? rep.rdtVEG : rep.hitVEG);
   meterve.SetValueDecimals(0);
   meterve.SetRangeDecimals(0);

   metero2.SetRange(rep.lotEGO, rep.hitEGO);
   metero2.SetTitle("Exhaust Gas Oxygen");
   metero2.SetUnit("volts");
   metero2.SetAlert(rep.rdtEGO > 0.0 ? rep.rdtEGO : mdb.Const(Degoswitchv)/255.0*5.0);
   metero2.SetValueDecimals(2);
   metero2.SetRangeDecimals(2);

   meterwarm.SetRange(rep.lotWU, rep.hitWU);
   meterwarm.SetTitle("Warmup Enrichment");
   meterwarm.SetUnit("%");
   meterwarm.SetAlert(rep.rdtWU > 0.0 ? rep.rdtWU : rep.hitWU);
   meterwarm.SetValueDecimals(0);
   meterwarm.SetRangeDecimals(0);

   meteraccel.SetRange(rep.lotACE, rep.hitACE);
   meteraccel.SetTitle("Acceleration Enrichment");
   meteraccel.SetUnit("%");
   meteraccel.SetAlert(rep.rdtACE > 0.0 ? rep.rdtACE : rep.hitACE);
   meteraccel.SetValueDecimals(0);
   meteraccel.SetRangeDecimals(0);

   timer(on);

   return TRUE;
}

//------------------------------------------------------------------------------

static char *locMsg(int curX, int curY, int vebin, char *suffix)
{
   static char message[200];
   sprintf(message, "(%4d RPM, %3d%s) = %3d%% %s", mdb.rpmrange[curX], mdb.maprange[curY], mdb.alphaN() == 1 ? "%": " kPa", mdb.Const(Dve+vebin), suffix);
   return message;
}

void Dttune::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
   if (nChar == 'S') {   // Burn flash - "s"
      OnBurn();
   }
   else if (nChar == 'X') {   // Exit
      timer(off);
      if (MessageBox("Quit tuning?", "Tuning Go Bye-Bye", MB_YESNO | MB_DEFBUTTON2 | MB_ICONQUESTION | MB_SYSTEMMODAL) == IDYES) {
         CDialog::OnOK();
      }
      timer(on);
   }
   else if (nChar == VK_F3 || nChar == 'Q') {   // Richen VE - F3 or Q
      if (mdb.Const(Dve+ivebin) < 255) {
         mdb.putConstByte(Dve+ivebin, mdb.Const(Dve+ivebin)+1, true);

         setMsg(locMsg(currcurx, currcury, ivebin, "Enriching"), 1, wht);
         setMsg("VE Table not saved to FLASH", 2, red);

         veBar->SetPos(int(100.0 * (mdb.Const(Dve+ivebin)-rep.lotVEB) / (rep.hitVEB-rep.lotVEB)));
      }
   }
   else if (nChar == VK_F4 || nChar == 'W') {    // Enlean VE - F4 or "W"
      if (mdb.Const(Dve + ivebin) > 0) {
         mdb.putConstByte(Dve+ivebin, mdb.Const(Dve+ivebin)-1, true);

         setMsg(locMsg(currcurx, currcury, ivebin, "Enleaning"), 1, wht);
         setMsg("VE Table not saved to FLASH", 2, red);

         veBar->SetPos(int(100.0 * (mdb.Const(Dve+ivebin)-rep.lotVEB) / (rep.hitVEB-rep.lotVEB)));
      }
   }
   else {
      int dx = 0;
      int dy = 0;

      if (nChar == VK_RETURN) { // Return key == next intersection.
         if (currcurx < 7)
            dx = 1;
         else {
            dx = -7;
            dy = currcury < 7 ? 1 : -7;
         }
      }

      if (nChar == VK_F5 || nChar == 'H' || nChar == VK_LEFT) {
         if (currcurx > 0) dx = -1;
      }
      if (nChar == VK_F7 || nChar == 'K' || nChar == VK_UP) {
         if (currcury > 0) dy = -1;
      }
      if (nChar == VK_F6 || nChar == 'J' || nChar == VK_DOWN) {
         if (currcury < 7) dy = +1;
      }
      if (nChar == VK_F8 || nChar == 'L' || nChar == VK_RIGHT) {
         if (currcurx < 7) dx = +1;
      }

      if (dx || dy) {
         CDC *dc      = GetDC();
         int nOldMode = dc->GetROP2();
         dc->SetROP2(R2_XORPEN);

            dc->SelectObject(&pcursor);

            // Erase cursor.
            int xval = int(sloperpm * (double(mdb.rpmrange[currcurx]) - minrpm) + double(vepx1));
            int yval = int(slopemap * (double(mdb.maprange[currcury]) - minmap) + double(vepy1));
            dc->MoveTo(xval-2, yval-2); dc->LineTo(xval-8, yval-8);
            dc->MoveTo(xval-2, yval+2); dc->LineTo(xval-8, yval+8);
            dc->MoveTo(xval+2, yval-2); dc->LineTo(xval+8, yval-8);
            dc->MoveTo(xval+2, yval+2); dc->LineTo(xval+8, yval+8);

            // Reposition cursor.
            currcurx += dx;
            currcury += dy;
            ivebin    = currcurx + 8 * currcury;

            // Draw new one.
            xval = int(sloperpm * (double(mdb.rpmrange[currcurx]) - minrpm) + double(vepx1));
            yval = int(slopemap * (double(mdb.maprange[currcury]) - minmap) + double(vepy1));
            dc->MoveTo(xval - 2, yval - 2); dc->LineTo(xval - 8, yval - 8);
            dc->MoveTo(xval - 2, yval + 2); dc->LineTo(xval - 8, yval + 8);
            dc->MoveTo(xval + 2, yval - 2); dc->LineTo(xval + 8, yval - 8);
            dc->MoveTo(xval + 2, yval + 2); dc->LineTo(xval + 8, yval + 8);

         dc->SetROP2(nOldMode);
         ReleaseDC(dc);
      }

      setMsg(locMsg(currcurx, currcury, ivebin, ""), 1, wht);
   }

   CDialog::OnKeyDown(nChar, nRepCnt, nFlags);
}

//------------------------------------------------------------------------------

void Dttune::OnPaint()
{
   CPaintDC dc(this);

   dc.SelectObject(&pwhite);

   // Outline graphical map.
   CRect rectsav = veplotrect;
   dc.Rectangle(rectsav);
   rectsav.DeflateRect(3, 3);
   dc.Draw3dRect(rectsav, (~wht) & 0xFFFFFF, (~blk) & 0xFFFFFF);
   rectsav.DeflateRect(1, 1);
   dc.Draw3dRect(rectsav, (~lgr) & 0xFFFFFF, (~dgr) & 0xFFFFFF);
   rectsav.DeflateRect(4, 4);
   dc.FillRect(rectsav, &brushblk);

   // Outline message window.
   rectsav = vewarmrect;
   dc.Rectangle(rectsav);
   rectsav.DeflateRect(3, 3);
   dc.Draw3dRect(rectsav, (~wht) & 0xFFFFFF, (~blk) & 0xFFFFFF);
   rectsav.DeflateRect(1, 1);
   dc.Draw3dRect(rectsav, (~lgr) & 0xFFFFFF, (~dgr) & 0xFFFFFF);
   rectsav.DeflateRect(4, 4);
   dc.FillRect(rectsav, &brushblk);

   // Draw the plot grid.
   int i, j, xval, yval;
   for (i = 0; i < 8; i++) {
      xval = int(sloperpm * (double(mdb.rpmrange[i]) - minrpm) + vepx1);
      for (j = vepy1; j < vepy2; j += 4) {
         dc.SetPixel(xval, j, mgr);
      }
   }
   for (i = 0; i < 8; i++) {
      yval = int(slopemap * (double(mdb.maprange[i]) - minmap) + vepy1);
      for (j = vepx1; j < vepx2; j += 4) {
         dc.SetPixel(j, yval, mgr);
      }
   }

   // Update the gauges.
   CRect r;
//   this->GetWindowRect(&r);
   GetDlgItem(IDC_METERRPM)->GetWindowRect(&r);
   ScreenToClient(&r);
   meterrpm.ShowMeter(&dc, r);

   GetDlgItem(IDC_METERMAP)->GetWindowRect(&r);
   ScreenToClient(&r);
   metermap.ShowMeter(&dc, r);

   GetDlgItem(IDC_METERVE)->GetWindowRect(&r);
   ScreenToClient(&r);
   meterve.ShowMeter(&dc, r);

   GetDlgItem(IDC_METERO2)->GetWindowRect(&r);
   ScreenToClient(&r);
   metero2.ShowMeter(&dc, r);

   GetDlgItem(IDC_METERWARM)->GetWindowRect(&r);
   ScreenToClient(&r);
   meterwarm.ShowMeter(&dc, r);

   GetDlgItem(IDC_METERACCEL)->GetWindowRect(&r);
   ScreenToClient(&r);
   meteraccel.ShowMeter(&dc, r);

   int nOldMode = dc.GetROP2();
   dc.SetROP2(R2_XORPEN);

      // Draw the spot.
      dc.SelectObject(&pspot);
      dc.Ellipse(xrpm - 5, ymap - 5, xrpm + 5, ymap + 5);

      // Draw the cursor.
      dc.SelectObject(&pcursor);
      xval = int(sloperpm * (double(mdb.rpmrange[currcurx]) - minrpm) + (double) vepx1);
      yval = int(slopemap * (double(mdb.maprange[currcurx]) - minmap) + (double) vepy1);
      dc.MoveTo(xval - 2, yval - 2); dc.LineTo(xval - 8, yval - 8);
      dc.MoveTo(xval - 2, yval + 2); dc.LineTo(xval - 8, yval + 8);
      dc.MoveTo(xval + 2, yval - 2); dc.LineTo(xval + 8, yval - 8);
      dc.MoveTo(xval + 2, yval + 2); dc.LineTo(xval + 8, yval + 8);

   dc.SetROP2(nOldMode);

   // Send the messages.
//   dc.SetBkMode(TRANSPARENT);
   setMsg("MegaTune - F1 for help", 0, wht);
   if (!mdb.loaded()) mdb.getConst();
}

//------------------------------------------------------------------------------

void Dttune::OnTimer(UINT nIDEvent)
{
   unsigned char rtv[Rget];
   if (!mdb.getRuntime(rtv))
      setMsg("Disconnected from controller", 3, red);
   else {
      setMsg("Connected to controller", 3, wht);
      updateDisplay(rtv);
   }
   CDialog::OnTimer(nIDEvent);
}

//------------------------------------------------------------------------------

BEGIN_EVENTSINK_MAP(Dttune, CDialog)
   //{{AFX_EVENTSINK_MAP(Dttune)
   //}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()

//------------------------------------------------------------------------------

void Dttune::updateDisplay(unsigned char runtimevars[])
{
   unsigned int   it, ik, ir, im;
   double         ft, slope, tmp1, tmp2, press;

   slope = -0.9706;
   ft    = slope * (double(runtimevars[Rclt]) - 32.49) + 170.0;

   // Put warmup tuning screen info here - next release!

   CDC *dc = GetDC();
   double rpm = runtimevars[Rrpm] / 10.0;
   meterrpm.UpdateNeedle(dc, rpm*1000.0);

   it   = 0;
   tmp1 = 1.0E30;
   for (ik = 0; ik < 8; ik++) {
      slope = mdb.Const(Drpmrangeve + ik) * 100.0;
      tmp2 = ft - slope;
      tmp2 = tmp2 * tmp2;
      if (tmp2 < tmp1) {
         tmp1 = tmp2;
         it   = ik;
      }
   }
   ir = it;

   // Put in automatic VE cursor code here - next release!
   press = mdb.alphaN() ? mdb.throttle : mdb.map;
   metermap.UpdateNeedle(dc, press);

   it   = 0;
   tmp1 = 1.0E30;
   for (ik = 0; ik < 8; ik++) {
      slope = mdb.Const(Dkparangeve + ik);
      tmp2  = press - slope;
      tmp2  = tmp2 * tmp2;
      if (tmp2 < tmp1) {
         tmp1 = tmp2;
         it = ik;
      }
   }
   im = it;

   ivebin = ir + 8 * im;
   // put in automatic VE cursor code here - next release!
   // VE section

   double yo2 = runtimevars[Rego] * 0.0195312;
   metero2.UpdateNeedle(dc, yo2);

   ivebin = currcurx + 8 * currcury;
   double ytit = mdb.Const(Dve + ivebin);
   meterve.UpdateNeedle(dc, ytit);
   veBar->SetPos(int(100.0 * (ytit-rep.lotVEB) / (rep.hitVEB-rep.lotVEB)));

   ytit = runtimevars[Rtpsaccel];
   meteraccel.UpdateNeedle(dc, ytit);

   meterwarm.UpdateNeedle(dc, double(runtimevars[Rwarmcorr]));

   int nOldMode = dc->GetROP2();
   dc->SetROP2(R2_XORPEN);
      dc->SelectObject(&pspot);
      dc->Ellipse(xrpm - 5, ymap - 5, xrpm + 5, ymap + 5);
      xrpm = int(sloperpm * ((1000.0 * rpm) - minrpm) + vepx1);
      ymap = int(slopemap * ((press) - minmap) + vepy1);
      dc->Ellipse(xrpm - 5, ymap - 5, xrpm + 5, ymap + 5);
   dc->SetROP2(nOldMode);
   ReleaseDC(dc);
}

//------------------------------------------------------------------------------

BOOL Dttune::OnCommand(WPARAM wParam, LPARAM lParam)
{
   if (wParam == IDCANCEL) {
      timer(off);
      if (MessageBox("Quit tuning?", "Tuning Go Bye-Bye", MB_YESNO | MB_DEFBUTTON2 | MB_ICONQUESTION | MB_SYSTEMMODAL) != IDYES) {
         timer(on);
         return TRUE;
      }
   }
   return CDialog::OnCommand(wParam, lParam);
}

//------------------------------------------------------------------------------

void Dttune::OnBurn()
{
   mdb.burnConst();
   setMsg("VE Burned into Flash RAM", 2, grn);
}


//------------------------------------------------------------------------------

void Dttune::OnReqfuelSpin(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_UPDOWN* pNMUpDown = reinterpret_cast<NM_UPDOWN *>(pNMHDR);
   spinReqFuel(pNMUpDown->iDelta);
	*pResult = 0;
}

//------------------------------------------------------------------------------

BOOL Dttune::spinReqFuel(int delta)
{
   unsigned char reqFuel = mdb.Const(Dreq_fuel);
   if (delta == 1 && reqFuel > 1) {
      mdb.putConstByte(Dreq_fuel, reqFuel-1, true);
   }
   if (delta == -1 && reqFuel < 255) {
      mdb.putConstByte(Dreq_fuel, reqFuel+1, true);
   }

   mdb.reqFuel = mdb.Const(Dreq_fuel) / 10.0;
   char s[10];
   sprintf(s, "%.1f", mdb.reqFuel);
   m_tReqFuel.SetWindowText(s);

   return TRUE;
}

//------------------------------------------------------------------------------

BOOL Dttune::PreTranslateMessage(MSG* pMsg)
{
   static bool shifted = false;
   static bool ctrled  = false;

   if (pMsg->message == WM_KEYUP) {
      switch (pMsg->wParam) {
         case VK_SHIFT  : shifted = false; return TRUE;
         case VK_CONTROL: ctrled  = false; return TRUE;
      }
   }
   if (pMsg->message == WM_KEYDOWN) {
      switch (pMsg->wParam) {
         case VK_SHIFT  : shifted = true;  return TRUE;
         case VK_CONTROL: ctrled  = true;  return TRUE;

         case VK_LEFT:  if (shifted) return TRUE; // Avoid accidental cursor motion.
         case 'H':
            OnKeyDown(pMsg->wParam, 1, pMsg->lParam >> 16);
            return TRUE;
         case VK_UP:    if (ctrled && shifted) return spinReqFuel(-1);
                        if (shifted) pMsg->wParam = 'Q'; // Q = rich up 38
         case 'K':
            OnKeyDown(pMsg->wParam, 1, pMsg->lParam >> 16);
            return TRUE;
         case VK_RIGHT: if (shifted) return TRUE;
         case 'L':
            OnKeyDown(pMsg->wParam, 1, pMsg->lParam >> 16);
            return TRUE;
         case VK_DOWN:  if (ctrled && shifted) return spinReqFuel( 1);
                        if (shifted) pMsg->wParam = 'W'; // W = lean dn 40
         case 'J':
            OnKeyDown(pMsg->wParam, 1, pMsg->lParam >> 16);
            return TRUE;

         case 'Q':
         case 'W':
         case 'X':
         case 'S':
         case VK_RETURN:
            OnKeyDown(pMsg->wParam, 1, pMsg->lParam >> 16);
            return TRUE;
      }
   }
   return CDialog::PreTranslateMessage(pMsg);
}

//------------------------------------------------------------------------------

