// ACG_XXA_cppreference.cpp - translated into C++ from ACG_Package/Code_N_Scripts/Perl/ACG_XXA_reference.pl in Kagi's SDK.  This CGI is an *exact* functional replacement for Kagi's original Perl script with the following exceptions: (1) a ACG:OemApprovalCode of "no" is returned instead of "yes" for an invalide ACG:UserPurchaseDate, and (2) the formatting of ACG:ResultCodeMessage is different (the leading ", " is removed) in checkForInputBufferUnderFlow and checkForInputBufferOverFlow.  I believe both of these are bugs in the original Perl script.

// Andrew Choi (akochoi@shaw.ca) 2004-11-06.

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
#include <cstdlib>

const std::string kAcgVersion = "0100";  // Version 1.0.0
const std::string kBuildDate = "2004-11-06";  // Built on YYYY-MM-DD
const std::string kEightBitClean = "1";  // ACG can handle 8 bit characters 1/0
const std::string kProductList = "__GENERIC__";  // used by many different programs
const std::string kContactEmail = "KagiAcgGenInfo@kagi.com";  // @kagi.com email address
const std::string kMaxGeneration = "250";  // Maximum number of activation codes to be generated during 'Generate' request
const std::string kPlatform = "PPPMacX";  // platform this ACG was written on
const std::string kKagiVendorCode = "XXA";  // Kagi Vendor code

// Globals that the DEVELOPER can set
const bool IsDebugOn = false;  // fast way to do debugging tests
const bool IsStandAloneTest = false;  // Pull data from a file called acgdata.txt?
const bool IsParamDebug = false;  // See all the parameters dumped to STDOUT?

// Globals that the code sets and checks
bool Have8BitData = false;  // if 8 bit data is present and it is used, then set this

// Constants that change with a port of the code
const std::string kACGName = "ACG_XXA_cppreference.exe";
const bool kCanDoSingleLicense = true;  // Is single user license supported?
const bool kCanDoSiteLincence = true;  // Is site license supported?
const bool kCanDoBonusLicense = true;  // Is bonus license suppored?
const bool kCanDoWorldLicense = true;  // Is world wide license supported?
const bool kCanDoSpecialLicense = true;  // Is special license supported?
const bool kCanDoUpgradeLicense = true;  // Is upgrade license supported?
const int kMaxLicenseGeneration	= 250;  // the maximum number of licenses that this ACG will generate in one call

// Constants that DO NOT change with a port of the code
const std::string kACGInputVersion = "0200";
const std::string kStandAloneFileValue = "acgdata.txt";

const std::string kSingleLicenseValue = "single";
const std::string kSiteLicenseValue = "site";
const std::string kBonusLicenseValue = "bonus";
const std::string kWorldLicenseValue = "world";
const std::string kSpecialLicenseValue = "special";
const std::string kUpgradeLicenseValue = "upgrade";

const std::string kRequestGenerate = "Generate";
const std::string kRequestInformation = "Information";

const std::string kACGSucceed = "0";
const std::string kACGFail = "1";

const std::string kFalse = "0";
const std::string kTrue = "1";

const std::string kACG_Request = "ACG:Request";
const std::string kACG_InputVersion = "ACG:InputVersion";	
const std::string kACG_PurchaserName = "ACG:PurchaserName";
const std::string kACG_PurchaserName_8bit = "ACG:PurchaserName-8bit";
const std::string kACG_PurchaserEmail = "ACG:PurchaserEmail";
const std::string kACG_Postal = "ACG:Postal";
const std::string kACG_Postal_8bit = "ACG:Postal-8bit";
const std::string kACG_UserPurchaseDate = "ACG:UserPurchaseDate";
const std::string kACG_DateProcessed = "ACG:DateProcessed";
const std::string kACG_TimeStamp = "ACG:TimeStamp";
const std::string kACG_CardName = "ACG:CardName";
const std::string kACG_CardName_8bit = "ACG:CardName-8bit";
const std::string kACG_CustomerSeed = "ACG:CustomerSeed";
const std::string kACG_OEMSeed = "ACG:OEMSeed";
const std::string kACG_TransactionID = "ACG:TransactionID";
const std::string kACG_SQNM = "ACG:SQNM";
const std::string kACG_ProductName = "ACG:ProductName";
const std::string kACG_ProductName_8bit = "ACG:ProductName-8bit";
const std::string kACG_QuantityOrdered = "ACG:QuantityOrdered";
const std::string kACG_UnitPayment = "ACG:UnitPayment";
const std::string kACG_LicenseType = "ACG:LicenseType";
const std::string kACG_DebugFlag = "ACG:DebugFlag";

const std::string kOEMTransOK = "yes";
const std::string kOEMTransStop = "no";
const std::string kOEMTransReview = "review";

const std::string kOEMResCode_GoodGen = "0";
const std::string kOEMResCode_GoodInfo = "1";

const std::string kOEMResCode_BadRequest = "11";
const std::string kOEMResCode_BadIVersion = "12";
const std::string kOEMResCode_BadPurchaseName = "13";
const std::string kOEMResCode_BadPurchaserEmail = "14";
const std::string kOEMResCode_BadPostal = "15";
const std::string kOEMResCode_BadCardName = "16";
const std::string kOEMResCode_BadCustomerSeed = "17";
const std::string kOEMResCode_BadOEMSeed = "18";
const std::string kOEMResCode_BadQtyOrdered = "19";
const std::string kOEMResCode_BadLicenseType = "20";
const std::string kOEMResCode_BadLicensePayment = "21";

const std::string kOEMResCode_BadParameter = "50";
const std::string kOEMResCode_BadInternalState = "51";
const std::string kOEMResCode_BadAppleEventMaxExceeded = "52";

std::string PlusToSpace(const std::string s)
{
  std::string result;
  
  for (int i = 0; i < s.length(); i++)
    result += s[i] == '+' ? ' ' : s[i];
  
  return result;
}

char X2C(char c1, char c2)
{
  unsigned char digit;
  
  digit = c1 >= 'A' ? (c1 & 0xdf) - 'A' + 10 : c1 - '0';
  digit *= 16;
  digit += c2 >= 'A' ? (c2 & 0xdf) - 'A' + 10 : c2 - '0';
  
  return digit;
}

std::string UnescapeURL(const std::string s)
{
  std::string result;
  
  for (int i = 0; i < s.length(); i++)
    if (s[i] != '%' || i + 2 >= s.length())
      result += s[i];
    else {
      result += X2C(s[i + 1], s[i + 2]);
      i += 2;
    }
      
      return result;
}

std::map<std::string, std::string> ParseQuery(const std::string input)
{
  std::map<std::string, std::string> result;
  
  std::string::const_iterator i = input.begin();
  while (i != input.end()) {
    // Ignore leading separators.
    i = std::find_if(i, input.end(), std::bind2nd(std::not_equal_to<char>(), '&'));
    
    // Find end of next block.
    std::string::const_iterator j = std::find(i, input.end(), '&');
    
    //std::cout << std::string(i, j) << std::endl;
    std::string entry = UnescapeURL(PlusToSpace(std::string(i, j)));
    
    std::string::iterator k = std::find(entry.begin(), entry.end(), '=');
    
    if (k == entry.end()) {
      std::cout << "Malformed entry (" << entry << "): missing '=' separator." << std::endl;
      exit(1);
    }
    
    //std::cout << std::string(entry.begin(), k) << " - " << std::string(k + 1, entry.end()) << std::endl;
    result[std::string(entry.begin(), k)] = std::string(k + 1, entry.end());
    
    i = j;
  }
  
  return result;
}

std::map<std::string, std::string> CGIReadQuery()
{
  char *requestMethod = getenv("REQUEST_METHOD");
  if (!requestMethod || std::string(requestMethod) != "POST") {
    std::cout << "This script should be referenced with a METHOD of POST." << std::endl << "If you don't understand this, see this <A HREF=\"http://www.ncsa.uiuc.edu/SDG/Software/Mosaic/Docs/fill-out-forms/overview.html\">forms overview</A>." << std::endl;
    exit(1);
  }
  
  char *contentType = getenv("CONTENT_TYPE");
  if (!contentType || std::string(contentType) != "application/x-www-form-urlencoded") {
    std::cout << "This script can only be used to decode form results." << std::endl;
    exit(1);
  }
  
  int contentLength = atoi(getenv("CONTENT_LENGTH"));
  //std::cout << "Content length = " << cl << std::endl;
  
  std::string input;
  char c;
  while (std::cin.get(c))
    input += c;
  
  //std::cout << "Input:" << std::endl << input;
  //std::cout << "Input length = " << input.length() << std::endl;
  if (contentLength != input.length()) {
    std::cout << "CONTENT_LENGTH contains incorrect value." << std::endl;
    exit(1);
  }
  
  return ParseQuery(input);
}

std::string XMLScrub(std::string s)
{
  std::string s1;  
  for (int i = 0; i < s.length(); i++)
    if (s[i] == '&')
      s1 += "&amp;";
    else
      s1 += s[i];
  
  std::string s2;  
  for (int i = 0; i < s1.length(); i++)
    if (s1[i] == '<')
      s2 += "&lt;";
    else
      s2 += s1[i];

  return s2;  
}

std::string encode(unsigned char c0, unsigned char c1, unsigned char c2)
{
  static const char base64Code[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  
  std::string result;
  
  result += base64Code[c0 >> 2];
  result += base64Code[((c0 & 0x03) << 4) | ((c1 & 0xf0) >> 4)];
  result += base64Code[((c1 & 0x0f) << 2) | ((c2 & 0xc0) >> 6)];
  result += base64Code[c2 & 0x3f];
  
  return result;
}

std::string encode_base64(std::string s)
{
  std::string result;
  
  int i;
  for (i = 0; i < s.length() / 3 * 3; i += 3)
    result += encode(s[i], s[i+1], s[i+2]);
  
  if (s.length() - i == 1) {
    std::string last = encode(s[i], '\0', '\0');
    result += std::string(last, 0, 2) + "==";
  }
  else if (s.length() - i == 2) {
    std::string last = encode(s[i], s[i+1], '\0');
    result += std::string(last, 0, 3) + "=";
  }
  
  return result;
}

class AcgActivationHeader {
  std::string kAVer;
  std::string kRsC;
  std::string kRCM;
  std::string kTGd;
  std::string kEstSize;
  std::string kACode;

  static const std::string kAVerTag;
  static const std::string kRsCTag;
  static const std::string kRCMTag;
  static const std::string kTGdTag;
  static const std::string kHRCDTag;
  static const std::string kEstSizeTag;
  static const std::string kACodeTag;
  static const std::string kACGOutputVersion;
public:
  AcgActivationHeader() { }
  AcgActivationHeader(std::string OEMTrans, std::string ResCode, std::string Msg, std::string Total);
  void setEstSize(std::string est) { kEstSize = est; }
  std::string makeRecord();
  void setApprovalCode(std::string code);
  int recordSize() { return makeRecord().length(); }
  void dumpRecord() { std::cout << makeRecord(); }
};

const std::string AcgActivationHeader::kAVerTag = "ACG:OutputVersion";
const std::string AcgActivationHeader::kRsCTag = "ACG:ResultCode";
const std::string AcgActivationHeader::kRCMTag = "ACG:ResultCodeMessage";
const std::string AcgActivationHeader::kTGdTag = "ACG:TotalGenerated";
const std::string AcgActivationHeader::kHRCDTag = "ACG:ActivationHeader";
const std::string AcgActivationHeader::kEstSizeTag = "ACG:EstResponseSize";
const std::string AcgActivationHeader::kACodeTag = "ACG:OemApprovalCode";
const std::string AcgActivationHeader::kACGOutputVersion = "0200";

AcgActivationHeader::AcgActivationHeader(std::string OEMTrans, std::string ResCode, std::string Msg, std::string Total)
{
  kAVer = kACGOutputVersion;
  kRsC = ResCode;
  kRCM = Msg;
  kTGd = Total;
  kEstSize = "2048";
  kACode = kOEMTransStop;  // start in reject mode
  setApprovalCode(OEMTrans);
}

std::string AcgActivationHeader::makeRecord()
{  
  std::string line = "\n\t\t<" + kHRCDTag + ">"
  + "\n\t\t\t<" + kAVerTag + ">" + XMLScrub(kAVer) + "</" + kAVerTag + ">"
  + "\n\t\t\t<" + kACodeTag + ">" + XMLScrub(kACode) + "</" + kACodeTag + ">"
  + "\n\t\t\t<" + kRsCTag + ">" + XMLScrub(kRsC) + "</" + kRsCTag + ">"
  + "\n\t\t\t<" + kRCMTag + ">" + XMLScrub(kRCM) + "</" + kRCMTag + ">"
  + "\n\t\t\t<" + kTGdTag + ">" + XMLScrub(kTGd) + "</" + kTGdTag + ">"	
  + "\n\t\t\t<" + kEstSizeTag + ">" + XMLScrub(kEstSize)	+ "</" + kEstSizeTag + ">"
  + "\n\t\t</" + kHRCDTag + ">"
  + "\n\t\t<!-- Comment here maybe -->";
  
  return line;	
}

void AcgActivationHeader::setApprovalCode(std::string code)
{
  if (code != kOEMTransOK && code != kOEMTransStop && code != kOEMTransReview) {
    std::cout << "Unknown approval code (this should not happen, check called to AcgActivationHeader::AcgActivationHeader()" << std::endl;
    exit(1);
  }
  
  kACode = code;
}

class AcgActivationRecord {
  std::string kQty;
  std::string kWA;
  std::string kWE;
  std::string kCode;
    
  static const std::string kQtyTag;
  static const std::string kWATag;
  static const std::string kWETag;
  static const std::string kCodeTag;
  static const std::string kARCDTag;
public:
  AcgActivationRecord(std::string q, std::string kA, std::string kE, std::string a) : kQty(q), kWA(kA), kWE(kE), kCode(a) { }
  std::string makeRecord();
  int recordSize() { return makeRecord().length() + 10; }  
  void dumpRecord() { std::cout << makeRecord(); }
};

const std::string AcgActivationRecord::kQtyTag = "ACG:Quantity";
const std::string AcgActivationRecord::kWATag = "ACG:KeyWords";
const std::string AcgActivationRecord::kWETag = "ACG:KeyWords-8bit";
const std::string AcgActivationRecord::kCodeTag = "ACG:ActivationCode";
const std::string AcgActivationRecord::kARCDTag = "ACG:ActivationRecord";

std::string AcgActivationRecord::makeRecord()
{  
  std::string line = "\n\t\t<" + kARCDTag + ">"
  + "\n\t\t\t<" + kQtyTag + ">" + XMLScrub(kQty) + "</" + kQtyTag + ">"
  + "\n\t\t\t<" + kWATag+ ">" + XMLScrub(kWA) + "</" + kWATag + ">"
  + "\n\t\t\t<" + kWETag+ ">" + XMLScrub(kWE) + "</" + kWETag + ">"
  + "\n\t\t\t<" + kCodeTag + ">" + XMLScrub(kCode) + "</" + kCodeTag + ">"
  + "\n\t\t</" + kARCDTag + ">";
  
  return line;	
}

class AcgInformationRecord {
  std::string kIVer;
  std::string kEight;
  std::string kBD;
  std::string kPL;
  std::string kCE;
  std::string kMGL;
  std::string kPlat;
  std::string kVC;
  std::string kInputVer;

  bool isNull;
  
  static const std::string kIVerTag;
  static const std::string kEightTag;
  static const std::string kBDTag;
  static const std::string kPLTag;
  static const std::string kCETag;
  static const std::string kMGLTag;
  static const std::string kPlatTag;
  static const std::string kVCTag;
  static const std::string kInputVerTag;
  static const std::string kIRCDTag;
public:
  AcgInformationRecord() { isNull = true; }
  AcgInformationRecord(std::string AcgVersion, std::string BuildDate, std::string EightBitClean, std::string ProductList, std::string ContactEmail, std::string MaxGeneration, std::string Platform, std::string KagiVendorCode);
  std::string makeRecord();
  int recordSize() { return isNull ? 0 : makeRecord().length() + 10; }  
  void dumpRecord() { std::cout << makeRecord(); }
};

const std::string AcgInformationRecord::kIVerTag = "ACG:OEMVersion";
const std::string AcgInformationRecord::kEightTag = "ACG:EightBitClean";
const std::string AcgInformationRecord::kBDTag = "ACG:BuildData";
const std::string AcgInformationRecord::kPLTag = "ACG:ProductList";
const std::string AcgInformationRecord::kCETag = "ACG:ContactEmail";
const std::string AcgInformationRecord::kMGLTag = "ACG:MaxGenerationLimit";
const std::string AcgInformationRecord::kPlatTag = "ACG:Platform";
const std::string AcgInformationRecord::kVCTag = "ACG:VendorCode";
const std::string AcgInformationRecord::kInputVerTag = "ACG:ACGInputVersion";
const std::string AcgInformationRecord::kIRCDTag = "ACG:InformationRecord";

AcgInformationRecord::AcgInformationRecord(std::string AcgVersion, std::string BuildDate, std::string EightBitClean, std::string ProductList, std::string ContactEmail, std::string MaxGeneration, std::string Platform, std::string KagiVendorCode)
{
  isNull = false;
  
  kIVer = AcgVersion;
  kEight = EightBitClean;
  kBD = BuildDate;
  kPL = ProductList;
  kCE = ContactEmail;
  kMGL = MaxGeneration;
  kPlat = Platform;
  kVC = KagiVendorCode;
  kInputVer = kACGInputVersion;
}

std::string AcgInformationRecord::makeRecord()
{
  if (isNull)
    return "";
  else {
    std::string line = "\n\t\t<" + kIRCDTag + ">"
    + "\n\t\t\t<" + kIVerTag + ">" + XMLScrub(kIVer) + "</" + kIVerTag + ">"
    + "\n\t\t\t<" + kEightTag + ">" + XMLScrub(kEight) + "</" + kEightTag + ">"
    + "\n\t\t\t<" + kBDTag + ">" + XMLScrub(kBD) + "</" + kBDTag + ">"
    + "\n\t\t\t<" + kPLTag + ">" + XMLScrub(kPL) + "</" + kPLTag + ">"
    + "\n\t\t\t<" + kCETag + ">" + XMLScrub(kCE) + "</" + kCETag + ">"
    + "\n\t\t\t<" + kMGLTag + ">" + XMLScrub(kMGL) + "</" + kMGLTag + ">"
    + "\n\t\t\t<" + kPlatTag + ">" + XMLScrub(kPlat) + "</" + kPlatTag + ">"
    + "\n\t\t\t<" + kVCTag + ">" + XMLScrub(kVC) + "</" + kVCTag + ">"
    + "\n\t\t\t<" + kInputVerTag + ">" + XMLScrub(kInputVer) + "</" + kInputVerTag + ">"
    + "\n\t\t</" + kIRCDTag + ">";
    
    return line;
  }
}

class AcgOutput {
  AcgActivationHeader header;
  std::vector<AcgActivationRecord> ra_activationRecords;
  AcgInformationRecord info;

  static const std::string kDbgTag;
  static const std::string kXmlTag;
  static const std::string kORCDTag;
  static const std::string kXMLNSTag;
public:
  AcgOutput() : header(), ra_activationRecords(), info() { }
  void setInformationRecord(const AcgInformationRecord &newInfo) { info = newInfo; }
  void setHeader(const AcgActivationHeader &newHeader) { header = newHeader; }
  void addActivationRecord(AcgActivationRecord ar) { ra_activationRecords.push_back(ar); }
  void writeDebug(std::string line);
  void startContent();
  void dumpContent();
  void endContent();
};

const std::string AcgOutput::kDbgTag = "ACG:DebugMessage";
const std::string AcgOutput::kXmlTag = "<?xml version=\'1.0\' ?>";
const std::string AcgOutput::kORCDTag = "ACG:Output";
const std::string AcgOutput::kXMLNSTag = "xmlns:ACG=\'http://www.kagi.com/\'";

void AcgOutput::writeDebug(std::string line)
{
  if (IsDebugOn)
    std::cout << "\n\t<"+ kDbgTag + ">" + XMLScrub(line) + "<\\" + kDbgTag + ">" << std::endl;
}

void AcgOutput::startContent()
{
  std::cout << "\n\n" + kXmlTag;
  std::cout << "\n\t<" + kORCDTag + ' ' + kXMLNSTag + ">";
}

void AcgOutput::dumpContent()
{
  int totalSize = header.recordSize();
  
  if (ra_activationRecords.size() == 0)
    totalSize += info.recordSize();
  else
    for (std::vector<AcgActivationRecord>::iterator i = ra_activationRecords.begin(); i != ra_activationRecords.end(); ++i)
      totalSize += (*i).recordSize();
  
  std::ostringstream ossTotalSize;
  ossTotalSize << totalSize;
  header.setEstSize(ossTotalSize.str());
  
  header.dumpRecord();
  
  if (ra_activationRecords.size() == 0)
    info.dumpRecord();
  else
    for (std::vector<AcgActivationRecord>::iterator i = ra_activationRecords.begin(); i != ra_activationRecords.end(); ++i)
      (*i).dumpRecord();
}

void AcgOutput::endContent()
{
  std::cout << "\n\t</" + kORCDTag + ">\n";
}

std::map<std::string, std::string> ReadQueryFromFile(std::string filename)
{
  std::map<std::string, std::string> result;

  std::ifstream ifs(filename.c_str());
  std::string line;
  
  while (getline(ifs, line)) {
    //std::cout << line << std::endl;
    if (line.length() != 0 && line[0] != '#') {
      //std::cout << line << std::endl;
      
      std::string::iterator k = std::find(line.begin(), line.end(), '=');
      
      if (k == line.end()) {
	std::cout << "Malformed entry (" << line << "): missing '=' separator." << std::endl;
	exit(1);
      }
      
      //std::cout << std::string(entry.begin(), k) << " - " << std::string(k + 1, entry.end()) << std::endl;
      result[std::string(line.begin(), k)] = std::string(k + 1, line.end());
    }      
  }
  
  return result;
}

std::string param(const std::map<std::string, std::string> &query, const std::string &tag)
{
  const std::map<std::string, std::string>::const_iterator p = query.find(tag);
  if (p != query.end())
    return (*p).second;
  else
    return "";
}

std::string checkForNamesInputParam(const std::map<std::string, std::string> &query)
{
  std::string useThisName;
  
  if (param(query, kACG_PurchaserName_8bit).length() != 0) {
    useThisName = param(query, kACG_PurchaserName_8bit);
    Have8BitData = true;
  }
  else if (param(query, kACG_CardName_8bit).length() != 0) {
    useThisName = param(query, kACG_CardName_8bit);
    Have8BitData = true;
  }
  else if (param(query, kACG_PurchaserName).length() != 0)
    useThisName = param(query, kACG_PurchaserName);
  else if (param(query, kACG_CardName).length() != 0)
    useThisName = param(query, kACG_CardName);
  else if (param(query, kACG_PurchaserEmail).length() != 0)
    useThisName = param(query, kACG_PurchaserEmail);		
  else
    useThisName = "";

  return useThisName;
}

bool checkInputVersion(const std::string inVersion, AcgOutput &output)
{
  std::string inMajor(inVersion, 0, 2);
  char inMinor = inVersion[2];
  char inMacro = inVersion[3];
  
  std::string acceptVersion = kACGInputVersion;
  std::string acceptMajor(acceptVersion, 0, 2);
  char acceptMinor = acceptVersion[2];
  char acceptMacro = acceptVersion[3];

  if (acceptMajor != inMajor) {
    output.setHeader(AcgActivationHeader(kOEMTransStop, kOEMResCode_BadIVersion, "Line Spec major mis-match; expected " + kACGInputVersion + " retrieved " + inVersion, "0"));
    return false;
  }
  
  if (inMinor < acceptMinor) {
    output.setHeader(AcgActivationHeader(kOEMTransStop, kOEMResCode_BadIVersion, "Line Spec minor mis-match; expected " + kACGInputVersion + " retrieved " + inVersion, "0"));
    return false;
  }
  
  if (inMacro < acceptMacro ){
    output.setHeader(AcgActivationHeader(kOEMTransStop, kOEMResCode_BadIVersion, "Line Spec macro mis-match; expected " + kACGInputVersion + " retrieved " + inVersion, "0"));
    return false;
  }
  
  return true;
}

bool checkForInputBufferUnderFlow(const std::map<std::string, std::string> &query, AcgOutput &output)
{
  std::map<std::string, int> paramMinLength;
  paramMinLength[kACG_Request] = 1;
  paramMinLength[kACG_InputVersion] = 4;
  paramMinLength[kACG_PurchaserName] = 0;
  paramMinLength[kACG_PurchaserName_8bit] = 0;
  paramMinLength[kACG_PurchaserEmail] = 0;
  paramMinLength[kACG_Postal] = 0;
  paramMinLength[kACG_Postal_8bit] = 0;
  paramMinLength[kACG_UserPurchaseDate] = 8;
  paramMinLength[kACG_DateProcessed] = 8;
  paramMinLength[kACG_TimeStamp] = 10;
  paramMinLength[kACG_CardName] = 0;
  paramMinLength[kACG_CardName_8bit] = 0;
  paramMinLength[kACG_CustomerSeed] = 0;
  paramMinLength[kACG_OEMSeed] = 0;
  paramMinLength[kACG_TransactionID] = 11;
  paramMinLength[kACG_SQNM] = 1;
  paramMinLength[kACG_ProductName] = 0;
  paramMinLength[kACG_ProductName_8bit] = 0;
  paramMinLength[kACG_QuantityOrdered] = 1;
  paramMinLength[kACG_UnitPayment] = 5;
  paramMinLength[kACG_LicenseType] = 1;
  paramMinLength[kACG_DebugFlag] = 1;
  
  std::vector<std::string> underFlow;
  
  for (std::map<std::string, int>::iterator p = paramMinLength.begin(); p != paramMinLength.end(); ++p) {
    //std::cout << (*p).first << " = " << (*p).second << std::endl;
    std::string inputValue = param(query, (*p).first);
    if (inputValue.length() < (*p).second)
      underFlow.push_back((*p).first + "(" + inputValue + ")");
  }
  
  if (underFlow.size() > 0) {
    std::string line;
    for (std::vector<std::string>::iterator p = underFlow.begin(); p != underFlow.end(); ++p) {
      if (line.length() > 0)
	line += ", ";
      line += ">" + *p + "<";
    }
    
    output.setHeader(AcgActivationHeader(kOEMTransStop, kOEMResCode_BadParameter, "The following parameters do not have the required minimum data: " + line, "0"));
    
    return false;    
  }
  
  return true;
}

bool checkForInputBufferOverFlow(const std::map<std::string, std::string> &query, AcgOutput &output)
{
  std::map<std::string, int> paramMaxLength;
  paramMaxLength[kACG_Request] = 32;
  paramMaxLength[kACG_InputVersion] = 4;
  paramMaxLength[kACG_PurchaserName] = 256;
  paramMaxLength[kACG_PurchaserName_8bit] = 256;
  paramMaxLength[kACG_PurchaserEmail] = 256;
  paramMaxLength[kACG_Postal] = 256;
  paramMaxLength[kACG_Postal_8bit] = 256;
  paramMaxLength[kACG_UserPurchaseDate] = 10;
  paramMaxLength[kACG_DateProcessed] = 10;
  paramMaxLength[kACG_TimeStamp] = 32;
  paramMaxLength[kACG_CardName] = 100;
  paramMaxLength[kACG_CardName_8bit] = 100;
  paramMaxLength[kACG_CustomerSeed] = 256;
  paramMaxLength[kACG_OEMSeed] = 256;
  paramMaxLength[kACG_TransactionID] = 32;
  paramMaxLength[kACG_SQNM] = 4;
  paramMaxLength[kACG_ProductName] = 256;
  paramMaxLength[kACG_ProductName_8bit] = 256;
  paramMaxLength[kACG_QuantityOrdered] = 3;
  paramMaxLength[kACG_UnitPayment] = 11;
  paramMaxLength[kACG_LicenseType] = 256;
  paramMaxLength[kACG_DebugFlag] = 1;
  
  std::vector<std::string> overFlow;
  
  for (std::map<std::string, int>::iterator p = paramMaxLength.begin(); p != paramMaxLength.end(); ++p) {
    //std::cout << (*p).first << " = " << (*p).second << std::endl;
    std::string inputValue = param(query, (*p).first);
    if (inputValue.length() > (*p).second)
      overFlow.push_back((*p).first + "(" + inputValue + ")");
  }
  
  if (overFlow.size() > 0) {
    std::string line;
    for (std::vector<std::string>::iterator p = overFlow.begin(); p != overFlow.end(); ++p) {
      if (line.length() > 0)
	line += ", ";
      line += " >" + *p + "<";
    }
    
    output.setHeader(AcgActivationHeader(kOEMTransStop, kOEMResCode_BadParameter, "The following parameters have exceeded their maximum length: " + line, "0"));
    
    return false;
  }
  
  return true;
}

bool checkLicense(const std::map<std::string, std::string> &query, AcgOutput &output)
{
  std::string quantityOrdered = param(query, kACG_QuantityOrdered);
  if (atoi(quantityOrdered.c_str()) > kMaxLicenseGeneration) {
    std::ostringstream ossMaxLicenseGeneration;
    ossMaxLicenseGeneration << kMaxLicenseGeneration;
    
    output.setHeader(AcgActivationHeader(kOEMTransReview, kOEMResCode_BadQtyOrdered, "Maximum number of requested licenses exceeded Maximum defined as: " + ossMaxLicenseGeneration.str() + " requested number: " +  quantityOrdered, "0"));

    return false;
  } 

  std::string licenseType = param(query, kACG_LicenseType);
  if (licenseType != kSingleLicenseValue && licenseType != kSiteLicenseValue && licenseType != kBonusLicenseValue && licenseType != kWorldLicenseValue && licenseType != kUpgradeLicenseValue && licenseType.find(kSpecialLicenseValue) != 0) {
    output.setHeader(AcgActivationHeader(kOEMTransStop, kOEMResCode_BadLicenseType, "License value of >" + licenseType + "< is unknown by this ACG.", "0"));
    
    return false;
  }

  if (!kCanDoSingleLicense && licenseType == kSingleLicenseValue || !kCanDoSiteLincence && licenseType == kSiteLicenseValue || !kCanDoBonusLicense && licenseType == kBonusLicenseValue || !kCanDoWorldLicense && licenseType == kWorldLicenseValue || !kCanDoUpgradeLicense && licenseType == kUpgradeLicenseValue || !kCanDoSpecialLicense && licenseType.find(kSpecialLicenseValue) == 0) {
    output.setHeader(AcgActivationHeader(kOEMTransStop, kOEMResCode_BadLicenseType, "The license type requested(" + licenseType + ") is not supported by this ACG", "0"));
    
    return false;
  }
  
  return true;
}

bool checkDate(std::string date)
{
  std::istringstream issDate(date);
  
  unsigned int year;
  unsigned int month;
  unsigned int day;
  char s1;
  char s2;
  
  if (issDate >> year >> s1 >> month >> s2 >> day) {
    if (year < 1900 || year > 2100)
      return false;
    
    if (month < 1 || month > 12)
      return false;
    
    if (day < 1 || day > 31)
      return false;
    
    return true;
  }
  else
    return false;
}

bool verifyMyInputs(const std::map<std::string, std::string> &query, AcgOutput &output)
{
  if (param(query, kACG_Request) == kRequestInformation)
    return true;
  
  if (!checkInputVersion(param(query, kACG_InputVersion), output))
    return false;

  if (!checkForInputBufferUnderFlow(query, output))
    return false;

  if (!checkForInputBufferOverFlow(query, output))
    return false;
  
  if (!checkLicense(query, output))
    return false;
  
  std::string request = param(query, kACG_Request);
  if (request != kRequestInformation && request != kRequestGenerate) {
    output.setHeader(AcgActivationHeader(kOEMTransStop, kOEMResCode_BadRequest, "Bad value given for " + kACG_Request + "; retrieved " + request, "0"));
    
    return false;
  }
  
  std::string unitPayment = param(query, kACG_UnitPayment);
  std::istringstream issUnitPayment(unitPayment);
  float floatUnitPayment;
  if (!(issUnitPayment >> floatUnitPayment) || floatUnitPayment < 0.0) {
    output.setHeader(AcgActivationHeader(kOEMTransStop, kOEMResCode_BadLicensePayment, "Bad value given for " + kACG_UnitPayment + "; retrieved " + unitPayment, "0"));
    
    return false;
  }
  
  std::string userPurchaseDate = param(query, kACG_UserPurchaseDate);
  if (!checkDate(userPurchaseDate)) {
    output.setHeader(AcgActivationHeader(kOEMTransStop, kOEMResCode_BadParameter, "Format or bounds of " + kACG_UserPurchaseDate + " is bad; retrieved " + userPurchaseDate, "0"));
    
    return false;
  }
  
  std::string dateProcessed = param(query, kACG_DateProcessed);
  if (!checkDate(dateProcessed)) {
    output.setHeader(AcgActivationHeader(kOEMTransStop, kOEMResCode_BadParameter, "Format or bounds of " + kACG_DateProcessed + "is bad; retrieved " + dateProcessed, "0"));
    
    return false;
  }
  
  std::string sequenceNumber = param(query, kACG_SQNM);
  int sqnm = atoi(sequenceNumber.c_str());
  if (sqnm < 0 || sqnm > 9999 || sequenceNumber.length() != 4) {
    output.setHeader(AcgActivationHeader(kOEMTransStop, kOEMResCode_BadParameter, "SQNM value not in bounds; retrieved " + sequenceNumber, "0"));
    
    return false;
  }
  
  std::string debugFlag = param(query, kACG_DebugFlag);
  if (debugFlag != kFalse && debugFlag != kTrue) {
    output.setHeader(AcgActivationHeader(kOEMTransStop, kOEMResCode_BadParameter, "Debug value not in bounds; retrieved " + debugFlag, "0"));
    
    return false;
  }
  
  return true;
}

bool calculateMyInformation(const std::map<std::string, std::string> &query, AcgOutput &output, int &count)
{
  output.setInformationRecord(AcgInformationRecord(kAcgVersion, kBuildDate, kEightBitClean, kProductList, kContactEmail, kMaxGeneration, kPlatform, kKagiVendorCode));
	
  count = 0;
	
  return true;
}

bool calculateMyRegCode(const std::map<std::string, std::string> &query, AcgOutput &output, int &count)
{
  std::string name = checkForNamesInputParam(query);
  
  std::string quantityOrdered = param(query, kACG_QuantityOrdered);
  int howManyLicenses = atoi(quantityOrdered.c_str());
  
  std::string seed = param(query, kACG_OEMSeed);
  if (seed.length() == 0) {
    output.setHeader(AcgActivationHeader(kOEMTransStop, kOEMResCode_BadOEMSeed, "OEM Seed not supplied", "0"));
    return false;
  }
  
  int sum = 0;
  for (int i = 0; i < name.length(); i++)
    sum += (unsigned char)name[i];
  sum += atoi(seed.c_str());
  
  for (int whichLicense = 0; whichLicense < howManyLicenses; whichLicense++) {
    sum += whichLicense;

    std::ostringstream ossSum;
    ossSum << sum << ':' << whichLicense << ':' << name;
    
    std::string regCode = encode_base64(ossSum.str()); 			
    
    if (Have8BitData)
      output.addActivationRecord(AcgActivationRecord("1", "", name, regCode));
    else
      output.addActivationRecord(AcgActivationRecord("1", name, "", regCode));
  }
  
  count = howManyLicenses;

  return true;
}

int main(int argc, char * const argv[])
{
  AcgOutput output;
  
  std::map<std::string, std::string> query;

  if (IsStandAloneTest)
    query = ReadQueryFromFile(kStandAloneFileValue);
  else
    query = CGIReadQuery();
  
  std::cout << "Content-Type: text/xml" << std::endl << std::endl;

  if (IsParamDebug)
    for (std::map<std::string, std::string>::iterator p = query.begin(); p != query.end(); ++p)
      std::cout << (*p).first << " = " << (*p).second << std::endl;

  output.startContent();
  
  if (verifyMyInputs(query, output)) {
    int count;
    bool status;
    
    if (query[kACG_Request] == kRequestInformation)
      status = calculateMyInformation(query, output, count);
    else
      status = calculateMyRegCode(query, output, count);

    std::ostringstream ossCount;
    ossCount << count;
    
    if (status) {
      if (query[kACG_Request] == kRequestInformation)
	output.setHeader(AcgActivationHeader(kOEMTransOK, kOEMResCode_GoodInfo, "Good Information", ossCount.str()));
      else
	output.setHeader(AcgActivationHeader(kOEMTransOK, kOEMResCode_GoodGen, "Good Generation", ossCount.str()));
    }
  }
    
  output.dumpContent();
		
  output.endContent();
  
  return 0;
}

