//
// This runs on the server waiting for the boarduino to contact it.
//
//      Client                Packet                  Server
//      ------                ------                  ------
//  1)  reset()               INIT_REQUEST---->   2)  handle Init
//  3)  doPacketInit()     <--INIT_CMD
//  4)                        ACT (checksum)-->   5)  send code section
//  6)  doPacketTransfer() <--WRITE_TO_MEM
//  7)                        ACT (checksum)-->   8)  send data section
//  9)  doPacketTransfer() <--WRITE_TO_MEM
//  10)                       ACT (checksum)-->   11) Done
//  12) doPacket()         <--START_CMD
//
//  13) Virtual sends         SEND_CHANNEL-->     14) Hit the Net
//  15) doPacketTransfer() <--WRITE_TO_MEM
//  16)                       ACT (checksum)-->   17) Check the checksum
//  18) doPacketTransfer() <--WRITE_TO_MEM              (write a sigle byte to display msg)
//  19)                       ACT (checksum)-->   20) Ingnore this one.
//
//  General Packets:
//   { INIT_REQUEST, KERNEL_VERSION, memSize }  4 bytes
//      KERNEL_VERSION - 1 byte - The version of the Arduino kernel code.
//      memSize - 2 bytes - The availible heap space.
//   { INIT_CMD, codeSize, dataSize, bssSize } 7 bytes
//      codeSize - 2 bytes - How big to make the "code" section
//      dataSize - 2 bytes - How big to make the "data" section
//      bssSize  - 2 bytes - How big to make the "bss" section
//   { ACT, checkSum } 3 bytes
//      checkSum - 2 bytes - The sum of all the bytes in the previous packet.
//   { START_CMD }  1 byte - Start the VM
//   { STOP_CMD }  1 byte  - Stop the VM
//   { WRITE_TO_MEM, size1, 2BYTES(addr1), data1, size2, [addr2, data2] }
//      The VM is paused during memory transfers.  Sometimes a lock or pointer needs to
//      be update as an atomic operation.  That is why the write to WRITE_TO_MEM packet
//      has two parts.  The first is for the data and second it to update a lock or pointer.
//      size1 - 1 bytes - the number of data1 bytes
//      addr1 - 2 bytes - memory address to put the data1
//      data1 - n byte - size1 bytes of data (255 max)
//      size2 - 1 bytes - the number of data2 bytes or 0.  If 0, then addr2 and data2 are not sent.
//      addr2 - (optional) 2  bytes - memory address to put the data1
//      data2 - (optional) n  byte - size1 bytes of data (255 max)
//   { WRITE_TO_EEPROM, size, addr, data }
//      Same as WRITE_TO_MEM except this writes to the EEPROM and there is no lock support.
//      If multiple WRITE_TO_EEPROM packets are needed, use STOP_CMD and START_CMD around them.
//
//  Custom Packets:
//   { STATE_CHANGE, msgCode } 2 byte 
//      msgCode - 1 byte - one of the following values:
//        1 = Reset - VM is just starting up
//        2 = IR detected motion.
//        3 = Screen driver is completely idle.
//        4 = Screen driver's second buffer is now available. 
//        5 = Internal switch was pressed
//        6 = Left switch was pressed
//        7 = Right switch was pressed
//        8 = Left switch was pressed while right switch was held down
//        9 = Right switch was pressed while left switch was held down
//   { SEND_CHANNEL, channel } 2 bytes
//      channel - 1 byte - The channel number that needs more data.
//
#include <iostream>
#include <fstream>
#include <string>
#include <cstdio>
#include "curlfuncs.h"
#include "csvparser.h"

using namespace std;

#define MAX_STOCKS 100
string  symbol[MAX_STOCKS];
double  shares[MAX_STOCKS];
double  cost[MAX_STOCKS];
int     numOfStock = 0;
double  cash       = 0.0;
string  otherStocks;

// "buf" is used by fixNum.
#define BUFSIZE 100 
static char   buf[BUFSIZE+1];


void  readPortfolio()
  {
  string    line;
  string    cashStr;
  string    dummy;
  int       i = 0;
  ifstream  myfile( "portfolio.data" );

  if( myfile.is_open() )
    {
    while( !myfile.eof() )
      {
      // Since the last 2 lines are special, read ahead 2 lines (plus an empty line).
      line = cashStr;
      cashStr = otherStocks;
      otherStocks = dummy;

      // Read the next line
      getline( myfile, dummy );
      if( !line.empty()  &&  numOfStock < MAX_STOCKS )
        {
        int  pos = line.find( '\t' );
        if( pos > 0 )
          {
          symbol[numOfStock] = line.substr( 0, pos );
          line.erase( 0, pos+1 );
          pos = line.find( '\t' );
          if( pos > 0 )
            {
            shares[numOfStock] = atof( line.substr(0, pos).c_str() );
            line.erase( 0, pos+1 );
            cost[numOfStock] = atof( line.c_str() );

            // Only accept this entry if everything looks good.
            if( shares[numOfStock] > 0.0  &  cost[numOfStock] > 0.0 )
              numOfStock++;
            }
          }
        }
      }
    myfile.close();
    cash = atof( cashStr.c_str() );
    }
  else 
    throw "Unable to open file"; 

  // cout << "There are " << numOfStock << " stocks.<br>" << endl;
  // for( int i=0; i < numOfStock; i++ )
  //   {
  //   cout << "Stock #" << i << ": " << symbol[i] << " " << shares[i] << " for " << cost[i] << "<br>" << endl;
  //   }
  // cout << "Cash =" << cash << "<br>" << endl;
  // cout << "Other Stocks =" << otherStocks << "<br>" << endl;
  }


// Convert an int to a string with leading zeros.
// Examples:
//   fixNum(123, 3) = "12"
//   fixNum(1, 3)   = "001"
//   fixNum(-1, 5)  = "-00001"
// Inputs:
//   num    - the number to convert
//   digits - number of digits to return.
// Returns:
//   Address of a static. Warning: Good until next call.
//
char*  fixNum( long  num, int digits )
  {
  long          n = ( num<0 ? -num : num );
  int           i = BUFSIZE;
  buf[BUFSIZE] = '\000';

  // Build string backwards.
  while( i > 2  &&  i > BUFSIZE-digits )
    {
    buf[--i] = (char)(0x30+(n % 10));
    n /= 10;
    }

  if( num < 0 )
    buf[--i] = '-';
  return &( buf[i] );
  }

// Convert a currency (double) in to a string.  No cense for amounts
// of $500 or more.
// Examples:
//   money( 0.0 )        =       "$0.00"
//   money( 12.0 )       =      "$12.00"
//   money( -0.1 )       =      "-$0.10"
//   money( 123456.789 ) = "$123,456.79"
string  money( double  num )
  {
  string  str = "";
  // n = ABS(num) * 100
  long  n =  (long)( ((num<0.0) ? -num : num) * 100.0 + 0.5 );

  // Include cense if less than $1,000 and there are some.
  if( n < 100000 )
    str = (string)"." + fixNum( (n % 100), 2 );
  n /= 100;

  // add ",123"
  while( n >= 1000 )
    {
    str = (string)"," + fixNum((n % 1000), 3) + str;
    n /= 1000;
    }
  str = (string)fixNum( n, (n>=100 ? 3 : (n>=10 ? 2 : 1)) ) + str;

  // Add the sign and dollar sign.
  str = (string)( num<0.0 ? "-$" : "$" ) + str;

  return str;
  }


string  colorMoney( double  num )
  {
  string  str = "<font color=";

  if( num < 0.0 )
    str += "\"#FF0000\">"; // Red
  else
    str += "\"#008000\">"; // Green

  str += money( num ) + "</font>";
  return str;
  }


string  percent( double  num )
  {
  // n = ABS(num) * 100
  double  n =  ((num<0.0) ? -num : num) * 100.0;
  string  str = "<font color=";

  if( num < 0.0 )
    str += "\"#FF0000\">-"; // Red
  else
    str += "\"#008000\">"; // Green

  char   lineBuf[30];
  if( n > 100.0 )
    sprintf( lineBuf, "%.0f", n );
  else if( n > 20.0 )
    sprintf( lineBuf, "%.1f", n );
  else
    sprintf( lineBuf, "%.2f", n );

  return str + (string)lineBuf + "%</font>";
  }


//
// Generate the main HTML table.
//
double  genTable( string sPage )
  {
  // Create a parser for CSV file.
  CSVParser   parser( sPage );

  // Titles - First row
  cout << "  <table border=\"1\" cellspacing=\"0\" cellpadding=\"2\" width=\"100%\">"   << endl;
  cout << "    <tr>"                                                                    << endl;
  cout << "      <td width=\"60\" ><font face=\"Arial Black\">Sym</font></td>"          << endl;
  cout << "      <td width=\"200\"><font face=\"Arial Black\">Name</font></td>"         << endl;
  cout << "      <td width=\"50\" ><font face=\"Arial Black\">Quantity</font></td>"     << endl;
  cout << "      <td width=\"60\"><font face=\"Arial Black\">Price</font></td>"         << endl;
  cout << "      <td width=\"70\"><font face=\"Arial Black\">Value</font></td>"         << endl;
  cout << "      <td width=\"130\"><font face=\"Arial Black\">Today $ (%)</font></td>"  << endl;
  cout << "      <td width=\"130\"><font face=\"Arial Black\">Gains $ (%)</font></td>"  << endl;
  cout << "    </tr>"                                                                   << endl;

  string      symName;
  string      corpName;
  double      prevClose;
  double      price;
  double      totalShares = 0.0;
  double      totalValue  = 0.0;
  double      totalToday  = 0.0;
  double      totalGains  = 0.0;

  // Loop thru the rows of the CVS file.
  int  i = 0;
  while( parser.nextLine() )
    {
    // Now extract the columns from the line
    parser >> symName >> corpName >> prevClose >> price;

    double  value       = price * shares[i];
    double  dayGain     = ( price - prevClose ) * shares[i];
    double  dayPercent  = ( price / prevClose ) - 1.0;
    double  gains       = ( price * shares[i] ) - cost[i] ;
    double  gainsPercent= ( price / (cost[i]/shares[i]) ) - 1.0;

    totalShares += shares[i];
    totalValue  += value;
    totalToday  += dayGain;
    totalGains  += gains;

    cout << "    <tr>"                                                  << endl;
    cout << "      <td>"                 << symName         << "</td>"  << endl; // Sym
    cout << "      <td>"                 << corpName        << "</td>"  << endl; // Name
    cout << "      <td align=\"right\">" << shares[i]       << "</td>"  << endl; // Quantity
    cout << "      <td align=\"right\">" << money(price)    << "</td>"  << endl; // Price
    cout << "      <td align=\"right\">" << money(value)    << "</td>"  << endl; // Value
    cout << "      <td align=\"right\">" << colorMoney(dayGain) <<
                               " (" << percent(dayPercent) << ")</td>"  << endl; // Today
    cout << "      <td align=\"right\">" << colorMoney(gains) <<
                             " (" << percent(gainsPercent) << ")</td>"  << endl; // Gains
    cout << "    </tr>"                                                 << endl;
    i++;
    }

  
  // Totals - last row.
  cout << "    <tr>"                                                               << endl;
  cout << "      <td></td>"                                                        << endl; // Sym
  cout << "      <td></td>"                                                        << endl; // Name
  cout << "      <td align=\"right\"><b>" << totalShares            << "</b></td>" << endl; // Quantity
  cout << "      <td></td>"                                                        << endl; // Price
  cout << "      <td align=\"right\"><b>" << money(totalValue)      << "</b></td>" << endl; // Value
  cout << "      <td align=\"right\"><b>" << colorMoney(totalToday) <<
                            " (" << percent(totalToday/totalValue) << ")</b></td>" << endl; // Today
  cout << "      <td align=\"right\"><b>" << colorMoney(totalGains) <<
                            " (" << percent(totalGains/totalValue) << ")</b></td>" << endl; // Gains
  cout << "    </tr>"                                                              << endl;
  cout << "  </table>"                                                             << endl;

  return totalValue;
  }


//
// Main
//
int main(void)
{
  // Output HTML header
  cout << "Content-type: text/html"      << endl << endl;
  cout << "<html>"                       << endl;
  cout << "  <head>"                     << endl;
  cout << "    <title>Stocks</title>"    << endl;
  cout << "    <META content=3 http-equiv=Refresh>" << endl;
  cout << "</head>"                    << endl;
  cout << "<body>"                     << endl;

  readPortfolio();

  try
    {
    // Initially set a cookie jar to store/retrieve cookies
    CurlSetCookieFile("./cookie.jar");

    // Now retrieve a URL using the GET method.
    //
    // See: http://www.gummy-stuff.org/Yahoo-data.htm
    //  Fields:
    //    s  - Symbol
    //    n  - Name
    //    p  - Previous
    //    l1 - Last traded
    string  url = "http://finance.yahoo.com/d/quotes.csv?s=";
    for( int i=0; i < numOfStock-1; i++ )
      url += symbol[i] + "+";
    url += symbol[numOfStock-1] + "&f=snpl1";
  
    //
    //  Go get "url" in to sPage.
    //
    string  sPage;
    if( CurlGetUrl(url.c_str(), &sPage) )
      {
      cout << "<h2>Your Stock Portfolio </h2>" << endl;

      //
      // Generate the main table
      //
      double  totalValue = genTable( sPage );

      cout << "<h2>Including cash (" << money(cash) << ") = " << money(totalValue+cash) << "</h2>\n" << endl;
      cout << "<a href=\"portfolio.cgi\">Change Portfolio</a><br>\n" << endl;
      }
    else
      {
      cout << "<H1>Error - Could not fetch from URL: " << url << "</H1>" << endl;
      }
    }

  catch(...)
    {
    cout << "<H1>Error - Exception:</H1>" << endl;

    }


  // Always call this function last to free the resources.
  FreeCurlLibrary();

  cout << "</body>" << endl;
  cout << "</html>" << endl;


  return 0;
}
