//----------------------------------------------------------------------------
//  qcrun.cpp
//  Interpreter for ntermediate code
//  $Date: 2003/02/20 00:24:16 $
//  $Revision: 1.8 $
//----------------------------------------------------------------------------
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstdlib>
#include <vector>
#include <string>

#include "QBits.h"
#include "QCompilerCntl.h"
#include "QCalcUnit.h"

typedef unsigned int                Appoptions;
typedef std::vector<QCalcUnit *>    QCalcUnits;

const char * const  app_name        = "qcrun";
const char * const  app_version     = "1.0";

const Appoptions    opt_version     = 1;
const Appoptions    opt_help        = 1 << 1;
const Appoptions    opt_verbose     = 1 << 2;
const Appoptions    opt_output      = 1 << 3;
const Appoptions    opt_input       = 1 << 4;

//----------------------------------------------------------------------------
//  Function prototypes
//----------------------------------------------------------------------------
inline bool isSet(const Appoptions &o1, const Appoptions &o2) {
  return (o2 == (o1 & o2));
}

Appoptions  checkOption(const char * inOptions);
void        compile(QCompilerCntl * const , QCalcUnits * const);
void        forceQuit(void);
void        printUsage(void);
void        printVersion(void);
void        errorAndQuit(const char * const inMessage);
std::string setOutputFilename(const std::string &inString);

//----------------------------------------------------------------------------
/**
 *  Main
 */
int main(int argc, char **argv) {
  Appoptions      parameters_ready    = 0;
  QBits           *qBits              = NULL;
  QCompilerCntl   *qCCntl             = NULL;
  QCalcUnits      *qCalcUnits         = NULL;

  std::string               ofilename = "";
  std::string               ifilename = "";
  std::vector<std::string>  args;

  if (argc <= 1) {
    printVersion();
    printUsage();
    std::exit(0);
  }

  for (int i = 0; i < argc; i++) {
    std::string tmpstr = argv[i];
    args.push_back(tmpstr);
  }

  int vec_size = args.size();
  int arg_at   = 1;

  while (arg_at < vec_size) {
    if ('-' == args[arg_at][0] && args[arg_at].length() > 1) {
      Appoptions tmpoptions = checkOption(args[arg_at].c_str());

      //_____ HELP _____
      if (isSet(tmpoptions, opt_help)) {
        if (isSet(parameters_ready, opt_help)) {
          errorAndQuit("-h option can be used only once.");
        } else {
          parameters_ready |= opt_help;
        }
      }

      //_____ VERBOSE _____
      if (isSet(tmpoptions, opt_verbose)) {
        if (isSet(parameters_ready, opt_verbose)) {
          errorAndQuit("-v option can be used only once.");
        } else {
          parameters_ready |= opt_verbose;
        }
      }

      //_____ VERSION _____
      if (isSet(tmpoptions, opt_version)) {
        if (isSet(parameters_ready, opt_version)) {
          errorAndQuit("-V option can be used only once.");
        } else {
          parameters_ready |= opt_version;
        }
      }

      //_____ OUTPUT _____
      if (isSet(tmpoptions, opt_output)) {
        if (isSet(parameters_ready, opt_output)) {
          errorAndQuit("-o option can be used only once.");
        }

        if (arg_at < vec_size - 1) {
          ofilename         = args[++arg_at];
          parameters_ready |= opt_output;
        } else {
          errorAndQuit("too few parameters.");
        }
      }
      arg_at++;
      continue;
    }

    if (isSet(parameters_ready, opt_input)) {
      errorAndQuit("too many input file.");
    } else {
      ifilename = args[arg_at];
      parameters_ready |= opt_input;
    }
    arg_at++;
  }

  //_____ VERSION _____
  if (isSet(parameters_ready, opt_version)) printVersion();

  //_____ HELP ________
  if (isSet(parameters_ready, opt_help))    printUsage();

  //_____ OUTPUT ______
  if (isSet(parameters_ready, opt_input)) {
    if (false == isSet(parameters_ready, opt_output)) {
      ofilename = setOutputFilename(ifilename);
    }

    std::ifstream ifs(ifilename.c_str(), std::ios::in);
    if (!ifs) {
      std::cerr << "Cannot open the input file.\n";
      forceQuit();
    }

    if (isSet(parameters_ready, opt_verbose)) {
      std::cerr << "Input  file: " << ifilename << "\n"
                << "Output file: " << ofilename << "\n";
    }

    try {
      qCCntl      = new QCompilerCntl(ifs);
      qCalcUnits  = new QCalcUnits(0);
    } catch (const std::bad_alloc ex) {
      std::cerr << "Failed in memory allocation.\n";
      forceQuit();
    }

    compile(qCCntl, qCalcUnits);

    try {
      qBits       = new QBits(qCCntl->GetNumberOfQBits());
    } catch (const std::bad_alloc &ex) {
      std::cerr << "Failed in memory allocation.\n"
                << "This .mcd code needs at least "
                << (1 << (qCCntl->GetNumberOfQBits() + 1 + 3))
                << " bytes to allocate the qubits.\n";
      forceQuit();
    }

    for (unsigned int i = 0; i < qCalcUnits->size(); i++) {
      if (isSet(parameters_ready, opt_verbose)) {
        std::cerr << std::setw(15) << std::left
                  << (*qCalcUnits)[i]->GetName()
                  << " ... ";
      }
      (*qCalcUnits)[i]->Calc(qBits);
      if (isSet(parameters_ready, opt_verbose)) {
        std::cerr << "OK\n";
      }
    }

    qBits->SaveToFile(ofilename.c_str());
    delete qBits;
    delete qCCntl;

    for (unsigned int i = 0; i < qCalcUnits->size(); i++) {
      delete (*qCalcUnits)[i];
    }
    delete qCalcUnits;
    ifs.close();

    if (isSet(parameters_ready, opt_verbose)) {
      std::cerr << "done." << std::endl;
    }
  }

  return 0;
}

//----------------------------------------------------------------------------
/**
 *  Check options
 */
Appoptions checkOption(const char * inOptions) {
  Appoptions retVal = 0;
  inOptions++;
  while ('\0' != *inOptions) {
    switch (*inOptions) {
    case 'h':
      retVal |= opt_help;
      break;
    case 'o':
      retVal |= opt_output;
      break;
    case 'v':
      retVal |= opt_verbose;
      break;
    case 'V':
      retVal |= opt_version;
      break;
    default:
      errorAndQuit("there are some invalid options.");
      break;
    }
    inOptions++;
  }
  return retVal;
}

//----------------------------------------------------------------------------
/**
 *
 */
void compile(QCompilerCntl * const ioCompiler, QCalcUnits * const ioUnits) {
  QCalcUnit *cu = NULL;
  ioUnits->clear();

  if (true == ioCompiler->Compile()) {
    unsigned int cntl_size = ioCompiler->GetNumberOfControl();
    for (unsigned int i = 0; i < cntl_size; i++) {
      cu = ioCompiler->AllocateControl(i);
      if (NULL != cu) ioUnits->push_back(cu);
    }
  } else {
    errorAndQuit("Parse error occurred.\n");
  }
}

//----------------------------------------------------------------------------
/**
 *  Force quit
 */
void forceQuit(void) {
  std::cerr << "Quit forced.\n";
  std::exit(1);
}

//----------------------------------------------------------------------------
/**
 *  Print usages
 */
void printUsage(void) {
  std::cout << "Usage: " << app_name
            << " [-hvV] [-o output_file] input_file.mcd\n\n"
            << "    -h  show this help\n"
            << "    -v  verbose mode\n"
            << "    -V  show version\n\n"
            << std::flush;
}

//----------------------------------------------------------------------------
/**
 *  Print version
 */
void printVersion(void) {
  std::cout << "\nCopyright (C) 2002-2003 QCAD project\n"
            << app_name << " version " << app_version << "\n\n"
            << std::flush;
}

//----------------------------------------------------------------------------
/**
 *  Show message and quit
 */
void errorAndQuit(const char * const inMessage) {
  std::cerr << "Error: " << inMessage << "\n\n";
  printUsage();
  forceQuit();
}

//----------------------------------------------------------------------------
/**
 *  Set output filename
 */
std::string setOutputFilename(const std::string &inString) {
  std::string retString = "";

  unsigned int posdot = inString.rfind(".", inString.length() - 1);

  if (posdot == std::string::npos) {
    retString = inString;
  } else {
    retString.assign(inString, 0, posdot);
  }

  retString += ".qdt";

  return retString;
}

