/** \defgroup Parsing Parsing classes */

/** @addtogroup Parsing
    @{
*/

/** @file CCommandParser.cpp
    \brief This file contains the implementation of the CCommandParser class

    It parses the command and give tokens to the CCommandExecutor class.  
    It implements the singleton pattern.
*/

#include <cctype>
#include <cmath>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

#include "popassert.h"
#include "CVector3f.h"
#include "CColor.h"
#include "CCommand.h"
#include "CCommandParser.h"


// Static members

CCommandParser* CCommandParser::s_pInstance = NULL;


/**
   It returns true if the string is a valid floating point value.
*/
bool CCommandParser::StringToFloat(const std::string& StringNumber,
                                   float* pFloat)
{
  bool ReturnValue = true;
  int DotCounter = 0;

  for (int i = StringNumber.size() - 1; i >= 0 && ReturnValue; --i)
    {
      ReturnValue = false;

      if (isdigit(StringNumber[i]))
        ReturnValue = true;

      if (StringNumber[i] == '.' && DotCounter == 0)
        {
          ReturnValue = true;
          DotCounter++;
        }

      if (StringNumber[i] == '-' && i == 0)
        ReturnValue = true;
    }

  if (ReturnValue && pFloat)
    *pFloat = atof(StringNumber.c_str());

  return ReturnValue;
}



/**
   It returns true if the string is a valid integer value.
*/
bool CCommandParser::StringToInt(const std::string& StringNumber,
                                 int* pInt)
{
  bool ReturnValue = true;

  for (int i = StringNumber.size() - 1; i >= 0 && ReturnValue; --i)
    {
      ReturnValue = false;

      if (isdigit(StringNumber[i]))
        ReturnValue = true;

      if (StringNumber[i] == '-' && i == 0)
        ReturnValue = true;
    }

  if (ReturnValue && pInt)
    *pInt = atoi(StringNumber.c_str());

  return ReturnValue;
}





/**
   It takes care of error checking and display messages 
   if there is a syntax error.
*/
CCommand CCommandParser::Parse(std::string StrCommand) const
{
  int i;
  CCommand ReturnedCommand;

  // Remove the whitespace at the end of the string
  for (i = StrCommand.size() - 1;
       i >= 0 && (StrCommand[i] == ' ' || StrCommand[i] == '\t');
       --i);
  StrCommand.erase(i+1, StrCommand.size() - i);


  // Tokenization of the string ; all tokens are stocked in a vector.
  std::vector< std::string > Tokens;
  std::istringstream CommandStream(StrCommand);
  std::string TempString;

  if (!CommandStream.eof())
    {
      CommandStream >> TempString;

      while(!CommandStream.eof())
        {
          Tokens.push_back(TempString);
          CommandStream >> TempString;
        }
      Tokens.push_back(TempString);
    }

  if (Tokens.size() == 0)
    {
      ReturnedCommand.m_CommandID = CCommand::COMMAND_END;
      return ReturnedCommand;
    }


  // Getting the ID of the command
  for (i = 0;
       i < CCommand::COMMAND_END && Tokens[0] != CCommand::s_CommandString[i];
       ++i);

  ReturnedCommand.m_CommandID = (CCommand::TYPE_COMMAND) i;

  switch(ReturnedCommand.m_CommandID)
    {
      //////////////////////////////////
      //                              //
      //  "help" and "print" command  //
      //                              //
      //////////////////////////////////
    case CCommand::COMMAND_PRINT:
      {
        if (Tokens.size() == 1)
          {
            std::cout << "Parameter error: usage: " << Tokens[0]
                      << " STRING" << std::endl;
            ReturnedCommand.m_CommandID = CCommand::COMMAND_END;
          }
      }
    case CCommand::COMMAND_HELP:
      {
        if (Tokens.size() > 1)
          {
            for (i = 0;
                 i < CCommand::PARAMETER_END && Tokens[1] != CCommand::s_ParameterString[i];
                 ++i);

            ReturnedCommand.m_ParameterID = (CCommand::TYPE_PARAMETER) i;

            if (ReturnedCommand.m_ParameterID == CCommand::PARAMETER_END &&
                Tokens[1] != "all")
              {
                std::cout << "\'" << Tokens[1] << "\' is not a valid parameter." << std::endl;
                ReturnedCommand.m_CommandID = CCommand::COMMAND_END;
              }

            if (Tokens.size() > 2)
              {
                std::cout << "Warning: ignored parameter(s): ";
                for (i = 2; i < (signed) Tokens.size(); ++i)
                  std::cout << Tokens[i] << " ";
                std::cout << std::endl;
              }
          }

        return ReturnedCommand;
        break;
      }


      /////////////////////
      //                 //
      //  "run" command  //
      //                 //
      /////////////////////
    case CCommand::COMMAND_RUN:
      {
        if (Tokens.size() == 1)
          {
            return ReturnedCommand;
          }
        else
          {
            std::cout << "Warning: ignored parameter(s): ";
            for (i = 1; i < (signed) Tokens.size(); ++i)
              std::cout << Tokens[i] << " ";
            std::cout << std::endl;
          }

        break;
      }


      /////////////////////
      //                 //
      //  "set" command  //
      //                 //
      /////////////////////
    case CCommand::COMMAND_SET:
      {
        // Taking care of the remaining parameters for the "set" function
        ReturnedCommand.m_pData.Int = NULL;

        if (Tokens.size() > 1)
          {
            for (i = 0;
                 i < CCommand::PARAMETER_END && Tokens[1] != CCommand::s_ParameterString[i];
                 ++i);

            ReturnedCommand.m_ParameterID = (CCommand::TYPE_PARAMETER) i;
          }

        switch (ReturnedCommand.GetParameterType())
          {
            // String
          case CCommand::PARAMETERTYPE_STRING:
            {
              if (Tokens.size() == 3)
                {
                  ReturnedCommand.m_pData.String = new std::string(Tokens[2]);
                }
              else
                {
                  std::cout << "Parameter error: usage: " << Tokens[0] << " " << Tokens[1]
                            << " STRING" << std::endl;
                  ReturnedCommand.m_CommandID = CCommand::COMMAND_END;
                }
              break;
            }

            // Float
          case CCommand::PARAMETERTYPE_FLOAT:
            {
              bool Valid = true;
              if (Tokens.size() == 3)
                {
                  ReturnedCommand.m_pData.Float = new float;

                  if (!StringToFloat(Tokens[2], ReturnedCommand.m_pData.Float))
                    {
                      delete ReturnedCommand.m_pData.Float;
                      ReturnedCommand.m_pData.Float = NULL;
                      Valid = false;
                    }
                }
              else
                {
                  Valid = false;
                }
 
              if (!Valid)
                {
                  std::cout << "Parameter error: usage: " << Tokens[0] << " " << Tokens[1]
                            << " FLOAT" << std::endl;
                  ReturnedCommand.m_CommandID = CCommand::COMMAND_END;
                }

              break;
            }

            // Vector
          case CCommand::PARAMETERTYPE_VECTOR:
            {
              bool Valid = true;
              ReturnedCommand.m_pData.Vector = new CVector3f;
              if (Tokens.size() == 5)
                {
                  for (int k = 0; k < 3 && Valid; ++k)
                    {
                      Valid = Valid && StringToFloat(Tokens[k+2],
                                                     &(*ReturnedCommand.m_pData.Vector)[k]);
                    }
                }
              else
                {
                  Valid = false;
                }

              if (!Valid)
                {
                  delete ReturnedCommand.m_pData.Vector;
                  ReturnedCommand.m_pData.Vector = NULL;
                  std::cout << "Parameter error: usage: " << Tokens[0] << " " << Tokens[1]
                            << " FLOAT FLOAT FLOAT" << std::endl;
                  ReturnedCommand.m_CommandID = CCommand::COMMAND_END;
                }

              break;
            }

            // Color
          case CCommand::PARAMETERTYPE_COLOR:
            {
              bool Valid = true;
              ReturnedCommand.m_pData.Color = new CColor;
              if (Tokens.size() == 6)
                {
                  for (int k = 0; k < 4 && Valid; ++k)
                    {
                      Valid = Valid && StringToFloat(Tokens[k+2],
                                                     &(*ReturnedCommand.m_pData.Color)[k]);
                    }
                }
              else
                {
                  Valid = false;
                }

              if (!Valid)
                {
                  delete ReturnedCommand.m_pData.Color;
                  ReturnedCommand.m_pData.Color = NULL;
                  std::cout << "Parameter error: usage: " << Tokens[0] << " " << Tokens[1]
                            << " FLOAT FLOAT FLOAT FLOAT" << std::endl;
                  ReturnedCommand.m_CommandID = CCommand::COMMAND_END;
                }

              break;
            }

            // Int
          case CCommand::PARAMETERTYPE_INT:
            {
              bool Valid = true;
              if (Tokens.size() == 3)
                {
                  ReturnedCommand.m_pData.Int = new int;

                  if (!StringToInt(Tokens[2], ReturnedCommand.m_pData.Int))
                    {
                      delete ReturnedCommand.m_pData.Int;
                      ReturnedCommand.m_pData.Int = NULL;
                      Valid = false;
                    }
                }
              else
                {
                  Valid = false;
                }
 
              if (!Valid)
                {
                  std::cout << "Parameter error: usage: " << Tokens[0] << " " << Tokens[1]
                            << " INTEGER" << std::endl;
                  ReturnedCommand.m_CommandID = CCommand::COMMAND_END;
                }

              break;
            }

            // Default
          default:
            {
              std::cout << "Parameter error" << std::endl;
              ReturnedCommand.m_CommandID = CCommand::COMMAND_END;
              break;
            }
          }
      }

      //////////////////////
      //                  //
      //  "quit" command  //
      //                  //
      //////////////////////
    case CCommand::COMMAND_QUIT:
      {
        // Do nothing, just quit no matter what the
        // parameters are.
        break;
      }


      /////////////////////
      //                 //
      //  "end" command  //
      //                 //
      /////////////////////
    case CCommand::COMMAND_END:
    default:
      {
        std::cout << "\'" << Tokens[0] << "\' is not a valid command." << std::endl;
        break;
      }

    }

  return ReturnedCommand;
}



/** @} */
