/** \defgroup Parsing Parsing classes */

/** @addtogroup Parsing
    @{
*/

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

    It executes a command that is given to it by the CCommandParser.
*/

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

#include "popassert.h"
#include "CVector3f.h"
#include "CColor.h"
#include "CImage.h"
#include "CVectBase.h"
#include "compute.h"
#include "CMathExpression.h"
#include "CRenderObj.h"
#include "CCommand.h"
#include "CCommandParser.h"
#include "CCommandExecutor.h"


// Static members
const std::string HelpMessages[CCommand::PARAMETER_END+1] =
  {
    // PARAMETER_FORMULASTRING
    "The implicit equation of the surface to render.",
    // PARAMETER_SIGNINCREMENT
    "The step taken at each iteration in the signed mode.\n"
    "Lowering that value causes the rendering to be slower\n"
    "but more precise.\n"
    "\"help Sign\" for more info on the signed mode.",
    // PARAMETER_NOSIGNINCREMENT
    "The step taken at each iteration in the no signed mode.\n"
    "Lowering that value cause the rendering to be slower\n"
    "but more precise.\n"
    "\"help Sign\" for more info on the no signed mode.",
    // PARAMETER_NOSIGNPRECISION
    "The limit under which a floating point value is considered\n"
    "to be equal to zero.  It is used as the criterium to know\n"
    "if a point is on the surface in the no signed mode.\n"
    "\"help Sign\" for more info on the no signed mode.",
    // PARAMETER_ZOOM
    "The zoom factor of the camera.  If that value is set to 0,\n"
    "the camera is left in its position.  If the zoom is set to 1,\n"
    "the camera is placed so that all the viewing volume is seen.",
    // PARAMETER_MINVECTOR
    "The vector holding the minimum x, y and z value of the\n"
    "bouding box of the surface to be rendered.",
    // PARAMETER_MAXVECTOR
    "The vector holding the maximum x, y and z value of the\n"
    "bouding box of the surface to be rendered.",
    // PARAMETER_VIEWERPOS
    "The vector holding the position of the camera viewing\n"
    "the surface to be rendered.",
    // PARAMETER_LIGHTDIRECTION
    "The vector holding the direction of the directional light\n"
    "that is lightening the surface.",
    // PARAMETER_SURFACEAMBIENTCOLOR
    "The ambient color component of the surface.\n"
    "The color is in RGB with each component between 0 and 1.",
    // PARAMETER_SURFACEDIFFUSECOLOR
    "The diffuse color component of the surface.\n"
    "The color is in RGB with each component between 0 and 1.",
    // PARAMETER_SURFACESPECULARCOLOR
    "The specular color component of the surface.\n"
    "The color is in RGB with each component between 0 and 1.",
    // PARAMETER_SURFACESPECULAREXPONENT
    "The specular color exponent of the surface.\n"
    "It influences the specular highlights.",
    // PARAMETER_BOXAMBIENTCOLOR
    "The ambient color component of the bounding box.\n"
    "The color is in RGB with each component between 0 and 1.",
    // PARAMETER_BOXDIFFUSECOLOR
    "The diffuse color component of the bounding box.\n"
    "The color is in RGB with each component between 0 and 1.",
    // PARAMETER_BOXSPECULARCOLOR
    "The specular color component of the bounding box.\n"
    "The color is in RGB with each component between 0 and 1.",
    // PARAMETER_BOXSPECULAREXPONENT
    "The specular color exponent of the box.\n"
    "It influences the specular highlights.",
    // PARAMETER_BACKGROUNDCOLOR
    "The background color of the scene.\n"
    "The color is in RGB with each component between 0 and 1.",
    // PARAMETER_WIDTH
    "The width of the picture that will be rendering, in pixels.",
    // PARAMETER_HEIGHT
    "The height of the picture that will be rendering, in pixels.",
    // PARAMETER_PERSPECTIVE
    "Option to choose to use perspective or orthonormal projection.\n"
    "usage: set Perspective {on,off}",
    // PARAMETER_BOUNDINGBOX
    "Option to choose to draw the bounding box or not.\n"
    "usage: set BoundingBox {on,off}",
    // PARAMETER_SIGN
    "Option to choose between the signed mode and the no signed mode.\n"
    "The signed mode uses the signed of the expression f(x,y,z )to\n"
    "know if the ray hit the surface.  When the sign changes, there\n"
    "is a hit.  The no signed mode uses a threshold under which\n"
    "f(x,y,z) is considered to be equal to zero, so that the ray hit\n"
    "the surface.\n"
    "usage: set Sign {on,off}",
    // PARAMETER_OPENGLSPECULAR
    "Option to choose between OpenGL specular lighting calculation\n"
    "and normal ray-tracing specular lighting."
    "usage: set OpenGLSpecular {on,off}",
    // PARAMETER_END
    "Commands: help  : help on a topic\n"
    "          run   : launches the tracing\n"
    "          set   : sets a parameter\n"
    "          print : prints the value of a parameter\n"
    "          quit  : exit the program\n"
    "\n"
    "Parameters: FormulaString\n"
    "            SignIncrement\n"
    "            NoSignIncrement\n"
    "            NoSignPrecision\n"
    "            Zoom\n"
    "            MinVector\n"
    "            MaxVector\n"
    "            ViewerPos\n"
    "            LightDirection\n"
    "            SurfaceAmbientColor\n"
    "            SurfaceDiffuseColor\n"
    "            SurfaceSpecularColor\n"
    "            SurfaceSpecularExponent\n"
    "            BoxAmbientColor\n"
    "            BoxDiffuseColor\n"
    "            BoxSpecularColor\n"
    "            SurfaceSpecularExponent\n"
    "            BackgroundColor\n"
    "            Width\n"
    "            Height\n"
    "\n"
    "            Perspective\n"
    "            BoundingBox\n"
    "            Sign\n"
    "            OpenGLSpecular"
  };


CCommandExecutor* CCommandExecutor::s_pInstance = NULL;

/**
   It simply opens the file for reading.  If it succeeds,
   the file exists.
*/
#ifdef WIN32

#include <fstream>

bool CCommandExecutor::FileExist(const std::string& FileName)
{
  std::ifstream File;
  bool Exist;

  File.open(FileName.c_str());
  Exist = !(File.fail());
  File.close();

  return(Exist);
}

#elif defined(LINUX)

#include <sys/types.h>
#include <sys/stat.h>

bool CCommandExecutor::FileExist(const std::string& FileName)
{
  struct stat TempStruct;
  return (!stat(FileName.c_str(), &TempStruct));
}

#else
#error "OS not supported"
#endif




/**
   It takes care of the action that is to be done by
   the command.
*/
void CCommandExecutor::Execute(const CCommand& command,
                               CRenderObj& surfaceobj) const
{
  switch (command.m_CommandID)
    {
      //////////////////////
      //                  //
      //  "help" command  //
      //                  //
      //////////////////////
    case CCommand::COMMAND_HELP:
      {
        std::cout << HelpMessages[command.m_ParameterID] << std::endl;
        break;
      }

      /////////////////////
      //                 //
      //  "run" command  //
      //                 //
      /////////////////////
    case CCommand::COMMAND_RUN:
      {
        std::string strParameterCheck = surfaceobj.Validate();

        if (strParameterCheck != "")
          {
            std::cout << "Ray tracing not started." << std::endl
                      << strParameterCheck << std::endl;
            break;
          }

        bool ReadyToGo = false;
        std::string Answer_1, Answer_2;

          while(!ReadyToGo)
            {
              std::cout << "Enter the name of the BMP file to create."
                        << std::endl;
              GetLine(std::cin, Answer_1);

              if (Answer_1 == "")
                {
                  std::cout << "Please write a non empty name." << std::endl;
                  continue;
                }
              else
                {
                  if (FileExist(Answer_1))
                    {
                      std::cout << "This file exists.  Overwrite? (yes, no, cancel)"
                                << std::endl;
                      GetLine(std::cin, Answer_2);

                      if ( Answer_2 == "y"  ||
                           Answer_2 == "ye" || 
                           Answer_2 == "yes"  )
                        {
                          ReadyToGo = true;
                        }
                      else if ( Answer_2 == "n" ||
                                Answer_2 == "no"  )
                        {
                          continue;
                        }
                      else
                        {
                          std::cout << "Canceled" << std::endl;
                          break;
                        }
                    }
                  else
                    {
                      ReadyToGo = true;
                    }
                }
            }

          if (ReadyToGo)
            {
              surfaceobj.Update();
              CColor TempColor;
              CImage image(surfaceobj.m_Width, surfaceobj.m_Height);

              std::cout << "Ray tracing started" << std::endl;
              for (int j = surfaceobj.m_Height - 1; j >= 0; --j)
                for (int i = surfaceobj.m_Width - 1; i >= 0; --i)
                  {
                    TempColor = surfaceobj.GetPixelColor(i, j);
                    TempColor.Clamp();

                    image(i,j,0) = TempColor[0];
                    image(i,j,1) = TempColor[1];
                    image(i,j,2) = TempColor[2];
                  }
              std::cout << "Ray tracing completed" << std::endl;

              std::cout << "File writing started" << std::endl;

              switch( image.WriteBmpFile(Answer_1) )
                {
                case 0:
                  {
                    std::cout << "File writing completed" << std::endl
                              << "Job done" << std::endl;
                    break;
                  }
                case -1:
                  {
                    std::cout << "Error: unable to open file '"
                              << Answer_1 << "'" << std::endl;
                    break;
                  }
                default:
                  {
                    std::cout << "Error occured while writing to file"
                              << std::endl;
                    break;
                  }
                }
            }

          break;
      }

      /////////////////////
      //                 //
      //  "set" command  //
      //                 //
      /////////////////////
    case CCommand::COMMAND_SET:
      {
        switch (command.m_ParameterID)
          {
          case CCommand::PARAMETER_FORMULASTRING:
            {
              surfaceobj.m_FormulaString = *command.m_pData.String;
              delete command.m_pData.String;
              break;
            }
          case CCommand::PARAMETER_SIGNINCREMENT:
            {
              surfaceobj.m_SignIncrement = *command.m_pData.Float;
              delete command.m_pData.Float;
              break;
            }
          case CCommand::PARAMETER_NOSIGNINCREMENT:
            {
              surfaceobj.m_NoSignIncrement = *command.m_pData.Float;
              delete command.m_pData.Float;
              break;
            }
          case CCommand::PARAMETER_NOSIGNPRECISION:
            {
              surfaceobj.m_NoSignPrecision = *command.m_pData.Float;
              delete command.m_pData.Float;
              break;
            }
          case CCommand::PARAMETER_ZOOM:
            {
              surfaceobj.m_Zoom = *command.m_pData.Float;
              delete command.m_pData.Float;
              break;
            }
          case CCommand::PARAMETER_MINVECTOR:
            {
              surfaceobj.m_MinVector = *command.m_pData.Vector;
              delete command.m_pData.Vector;
              break;
            }
          case CCommand::PARAMETER_MAXVECTOR:
            {
              surfaceobj.m_MaxVector = *command.m_pData.Vector;
              delete command.m_pData.Vector;
              break;
            }
          case CCommand::PARAMETER_VIEWERPOS:
            {
              surfaceobj.m_ViewerPos = *command.m_pData.Vector;
              delete command.m_pData.Vector;
              break;
            }
          case CCommand::PARAMETER_LIGHTDIRECTION:
            {
              surfaceobj.m_LightDirection = *command.m_pData.Vector;
              delete command.m_pData.Vector;
              break;
            }
          case CCommand::PARAMETER_SURFACEAMBIENTCOLOR:
            {
              surfaceobj.m_SurfaceAmbientColor = *command.m_pData.Color;
              delete command.m_pData.Color;
              break;
            }
          case CCommand::PARAMETER_SURFACEDIFFUSECOLOR:
            {
              surfaceobj.m_SurfaceDiffuseColor = *command.m_pData.Color;
              delete command.m_pData.Color;
              break;
            }
          case CCommand::PARAMETER_SURFACESPECULARCOLOR:
            {
              surfaceobj.m_SurfaceSpecularColor = *command.m_pData.Color;
              delete command.m_pData.Color;
              break;
            }
          case CCommand::PARAMETER_SURFACESPECULAREXPONENT:
            {
              surfaceobj.m_SurfaceSpecularExponent = *command.m_pData.Float;
              delete command.m_pData.Float;
              break;
            }
          case CCommand::PARAMETER_BOXAMBIENTCOLOR:
            {
              surfaceobj.m_BoxAmbientColor = *command.m_pData.Color;
              delete command.m_pData.Color;
              break;
            }
          case CCommand::PARAMETER_BOXDIFFUSECOLOR:
            {
              surfaceobj.m_BoxDiffuseColor = *command.m_pData.Color;
              delete command.m_pData.Color;
              break;
            }
          case CCommand::PARAMETER_BOXSPECULARCOLOR:
            {
              surfaceobj.m_BoxSpecularColor = *command.m_pData.Color;
              delete command.m_pData.Color;
              break;
            }
          case CCommand::PARAMETER_BOXSPECULAREXPONENT:
            {
              surfaceobj.m_BoxSpecularExponent = *command.m_pData.Float;
              delete command.m_pData.Float;
              break;
            }
          case CCommand::PARAMETER_BACKGROUNDCOLOR:
            {
              surfaceobj.m_BackgroundColor = *command.m_pData.Color;
              delete command.m_pData.Color;
              break;
            }
          case CCommand::PARAMETER_WIDTH:
            {
              surfaceobj.m_Width = *command.m_pData.Int;
              delete command.m_pData.Int;
              break;
            }
          case CCommand::PARAMETER_HEIGHT:
            {
              surfaceobj.m_Height = *command.m_pData.Int;
              delete command.m_pData.Int;
              break;
            }
          case CCommand::PARAMETER_PERSPECTIVE:
            {
              if (*command.m_pData.String == "on")
                {
                  surfaceobj.m_Options = surfaceobj.m_Options | OPTION_PERSPECTIVE;
                }
              else if (*command.m_pData.String == "off")
                {
                  surfaceobj.m_Options = surfaceobj.m_Options & ~OPTION_PERSPECTIVE;
                }
              else
                {
                  std::cout << "Parameter error: usage: set Perspective {on,off}"
                            << std::endl;
                }

              delete command.m_pData.String;
              break;
            }
          case CCommand::PARAMETER_BOUNDINGBOX:
            {
              if (*command.m_pData.String == "on")
                {
                  surfaceobj.m_Options = surfaceobj.m_Options | OPTION_BOUNDINGBOX;
                }
              else if (*command.m_pData.String == "off")
                {
                  surfaceobj.m_Options = surfaceobj.m_Options & ~OPTION_BOUNDINGBOX;
                }
              else
                {
                  std::cout << "Parameter error: usage: set BoundingBox {on,off}"
                            << std::endl;
                }

              delete command.m_pData.String;
              break;
            }
          case CCommand::PARAMETER_SIGN:
            {
              if (*command.m_pData.String == "on")
                {
                  surfaceobj.m_Options = surfaceobj.m_Options | OPTION_SIGN;
                }
              else if (*command.m_pData.String == "off")
                {
                  surfaceobj.m_Options = surfaceobj.m_Options & ~OPTION_SIGN;
                }
              else
                {
                  std::cout << "Parameter error: usage: set Sign {on,off}"
                            << std::endl;
                }

              delete command.m_pData.String;
              break;
            }
          case CCommand::PARAMETER_OPENGLSPECULAR:
            {
              if (*command.m_pData.String == "on")
                {
                  surfaceobj.m_Options = surfaceobj.m_Options | OPTION_OPENGLSPECULAR;
                }
              else if (*command.m_pData.String == "off")
                {
                  surfaceobj.m_Options = surfaceobj.m_Options & ~OPTION_OPENGLSPECULAR;
                }
              else
                {
                  std::cout << "Parameter error: usage: set OpenGLSpecular {on,off}"
                            << std::endl;
                }

              delete command.m_pData.String;
              break;
            }
          default:
            {
              POP_ILLEGAL("Not supposed to get here");
            }
          }

        break;
      }

      ///////////////////////
      //                   //
      //  "print" command  //
      //                   //
      ///////////////////////
    case CCommand::COMMAND_PRINT:
      {
        switch (command.m_ParameterID)
          {
          case CCommand::PARAMETER_FORMULASTRING:
            {
              std::cout << surfaceobj.m_FormulaString << std::endl;
              break;
            }
          case CCommand::PARAMETER_SIGNINCREMENT:
            {
              std::cout << surfaceobj.m_SignIncrement << std::endl;
              break;
            }
          case CCommand::PARAMETER_NOSIGNINCREMENT:
            {
              std::cout << surfaceobj.m_NoSignIncrement << std::endl;
              break;
            }
          case CCommand::PARAMETER_NOSIGNPRECISION:
            {
              std::cout << surfaceobj.m_NoSignPrecision << std::endl;
              break;
            }
          case CCommand::PARAMETER_ZOOM:
            {
              std::cout << surfaceobj.m_Zoom << std::endl;
              break;
            }
          case CCommand::PARAMETER_MINVECTOR:
            {
              std::cout << surfaceobj.m_MinVector << std::endl;
              break;
            }
          case CCommand::PARAMETER_MAXVECTOR:
            {
              std::cout << surfaceobj.m_MaxVector << std::endl;
              break;
            }
          case CCommand::PARAMETER_VIEWERPOS:
            {
              std::cout << surfaceobj.m_ViewerPos << std::endl;
              break;
            }
          case CCommand::PARAMETER_LIGHTDIRECTION:
            {
              std::cout << surfaceobj.m_LightDirection << std::endl;
              break;
            }
          case CCommand::PARAMETER_SURFACEAMBIENTCOLOR:
            {
              std::cout << surfaceobj.m_SurfaceAmbientColor << std::endl;
              break;
            }
          case CCommand::PARAMETER_SURFACEDIFFUSECOLOR:
            {
              std::cout << surfaceobj.m_SurfaceDiffuseColor << std::endl;
              break;
            }
          case CCommand::PARAMETER_SURFACESPECULARCOLOR:
            {
              std::cout << surfaceobj.m_SurfaceSpecularColor << std::endl;
              break;
            }
          case CCommand::PARAMETER_SURFACESPECULAREXPONENT:
            {
              std::cout << surfaceobj.m_SurfaceSpecularExponent << std::endl;
              break;
            }
          case CCommand::PARAMETER_BOXAMBIENTCOLOR:
            {
              std::cout << surfaceobj.m_BoxAmbientColor << std::endl;
              break;
            }
          case CCommand::PARAMETER_BOXDIFFUSECOLOR:
            {
              std::cout << surfaceobj.m_BoxDiffuseColor << std::endl;
              break;
            }
          case CCommand::PARAMETER_BOXSPECULARCOLOR:
            {
              std::cout << surfaceobj.m_BoxSpecularColor << std::endl;
              break;
            }
          case CCommand::PARAMETER_BOXSPECULAREXPONENT:
            {
              std::cout << surfaceobj.m_BoxSpecularExponent << std::endl;
              break;
            }
          case CCommand::PARAMETER_BACKGROUNDCOLOR:
            {
              std::cout << surfaceobj.m_BackgroundColor << std::endl;
              break;
            }
          case CCommand::PARAMETER_WIDTH:
            {
              std::cout << surfaceobj.m_Width << std::endl;
              break;
            }
          case CCommand::PARAMETER_HEIGHT:
            {
              std::cout << surfaceobj.m_Height << std::endl;
              break;
            }
          case CCommand::PARAMETER_PERSPECTIVE:
            {
              std::cout << ((surfaceobj.m_Options & OPTION_PERSPECTIVE) ? "on" : "off")
                        << std::endl;
              break;
            }
          case CCommand::PARAMETER_BOUNDINGBOX:
            {
              std::cout << ((surfaceobj.m_Options & OPTION_BOUNDINGBOX) ? "on" : "off")
                        << std::endl;
              break;
            }
          case CCommand::PARAMETER_SIGN:
            {
              std::cout << ((surfaceobj.m_Options & OPTION_SIGN) ? "on" : "off")
                        << std::endl;
              break;
            }
          case CCommand::PARAMETER_OPENGLSPECULAR:
            {
              std::cout << ((surfaceobj.m_Options & OPTION_OPENGLSPECULAR) ? "on" : "off")
                        << std::endl;
              break;
            }
          case CCommand::PARAMETER_END:
            {
              // print all
              CCommand FakeCommand;
              FakeCommand.m_CommandID = CCommand::COMMAND_PRINT;

              for (int i = 0; i < CCommand::PARAMETER_END; ++i)
                {
                  std::cout << CCommand::s_ParameterString[i] << ": ";
                  FakeCommand.m_ParameterID = (CCommand::TYPE_PARAMETER) i;
                  Execute(FakeCommand, surfaceobj);
                }

              break;
            }
          default:
            {
              POP_ILLEGAL("Not supposed to get here");
            }
          }

        break;
      }

      //////////////////////
      //                  //
      //  "quit" command  //
      //                  //
      //////////////////////
    case CCommand::COMMAND_QUIT:
      {
        std::cout << "Thanks for using MathRay." << std::endl;
        exit(0);
        break;
      }

    case CCommand::COMMAND_END:
      {
        std::cout << "type 'help' for more info" << std::endl;
        break;
      }

    default:
      {
        POP_ILLEGAL("Not supposed to get here");
      }
    }
}


/** @} */
