/////////////////////////////////////////////////////////////////////////
//                                                                     //
// This file contains the implementation of the CScanner class.        //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
#include "tokens.hpp"
#include "CLexer.hpp"
#include "CScanner.hpp"
#include "CSphere.hpp"
#include "CPlane.hpp"
#include "CTriangle.hpp"
#include "CQuadric.hpp"
#include "CUnion.hpp"
#include "CIntersection.hpp"
#include "CDifference.hpp"
#include "CLight.hpp"
#include "CScene.hpp"
using std::auto_ptr;
using std::cerr;
using std::endl;
using std::string;




/////////////////////////////////////////////////////////////////////////
//                                                                     //
// Class    : CScanner                                                 //
//                                                                     //
// Function : parse()                                                  //
//                                                                     //
// Comments : This is the entry point of the parsing.  It returns a    //
//            pointer to the scene object corresponding to the result  //
//            of the parsing if it succeeds.                           //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
CScene* CScanner::parse()
{
  CScene *pCScene = NULL;

  try
    {
      pCScene = scene();
    }
  catch (string message)
    {
      cerr << message << endl;
    }

  return pCScene;
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
// The following functions represents grammar rules of the specific    //
// language used to describe scenes.  Some rules were removed/included //
// into other to simplify parsing.                                     //
//                                                                     //
/////////////////////////////////////////////////////////////////////////

// The general non-terminal symbol representing the whole scene
// description
CScene* CScanner::scene()
{
  auto_ptr<CScene> pCScene(new CScene);
  pCScene->m_renderingModes = modes();
  statement_list(pCScene.get());
  m_pLexer->match(SToken::ENDOFFILE_TOKEN);
  return pCScene.release();
}

// The non-terminal symbol representing a list of rendering modes
int CScanner::modes()
{
  int mode = 0;
  int nextToken = m_pLexer->lookahead(0);

  switch(nextToken)
    {
    case (TOKEN_MODE_RAYTRACE):     mode = CScene::ModeRaytrace;     break;
    case (TOKEN_MODE_OPENGL):       mode = CScene::ModeOpenGL;       break;
    case (TOKEN_MODE_DEPTH):        mode = CScene::ModeDepth;        break;
    case (TOKEN_MODE_STEREOGRAM):   mode = CScene::ModeStereogram;   break;
    case (TOKEN_MODE_SHADERRENDER): mode = CScene::ModeShaderRender; break;
    case (TOKEN_MODE_SHADERTEXT):   mode = CScene::ModeShaderText;   break;
    default:                        mode = CScene::ModeNone;         break;
    }

  if (mode != CScene::ModeNone)
    {
      m_pLexer->match(nextToken);
      mode = mode | modes();
    }

  return mode;
}

// The non-terminal symbol representing a list of statements
void CScanner::statement_list(CScene *pCScene)
{
  int nextToken = m_pLexer->lookahead(0);

  if (nextToken == TOKEN_NEW)
    {
      // Trying to create a new object or light
      m_pLexer->match(TOKEN_NEW);

      switch(m_pLexer->lookahead(0))
        {
        case (TOKEN_LIGHT):
          {
            // Trying to create a new light
            m_pLexer->match(TOKEN_LIGHT);
            auto_ptr<CLight> pLight(new CLight);
            pCScene->addLight(pLight.get());
            property_list(pLight.release());
            break;
          }
        case (TOKEN_SPHERE):
          {
            // Trying to create a new sphere
            m_pLexer->match(TOKEN_SPHERE);
            auto_ptr<CSphere> pSurface(new CSphere(m_pLexer->match(TOKEN_NAME)));
            addSurface(pSurface.get());
            surfaceproperty_list(pSurface.release());
            break;
          }
        case (TOKEN_PLANE):
          {
            // Trying to create a new plane
            m_pLexer->match(TOKEN_PLANE);
            auto_ptr<CPlane> pSurface(new CPlane(m_pLexer->match(TOKEN_NAME)));
            addSurface(pSurface.get());
            surfaceproperty_list(pSurface.release());
            break;
          }
        case (TOKEN_TRIANGLE):
          {
            // Trying to create a new triangle
            m_pLexer->match(TOKEN_TRIANGLE);
            auto_ptr<CTriangle> pSurface(new CTriangle(m_pLexer->match(TOKEN_NAME)));
            addSurface(pSurface.get());
            surfaceproperty_list(pSurface.release());
            break;
          }
        case (TOKEN_QUADRIC):
          {
            // Trying to create a new quadric
            m_pLexer->match(TOKEN_QUADRIC);
            auto_ptr<CQuadric> pSurface(new CQuadric(m_pLexer->match(TOKEN_NAME)));
            addSurface(pSurface.get());
            surfaceproperty_list(pSurface.release());
            break;
          }
        case (TOKEN_UNION):
          {
            // Trying to create a new union of surfaces
            m_pLexer->match(TOKEN_UNION);
            const string name = m_pLexer->match(TOKEN_NAME);
            CSurface *pSubSurface1 = getSurface(m_pLexer->match(TOKEN_NAME));
            CSurface *pSubSurface2 = getSurface(m_pLexer->match(TOKEN_NAME));
            auto_ptr<CBooleanSurface> pSurface(new CUnion(name,
                                                          pSubSurface1,
                                                          pSubSurface2));
            addSurface(pSurface.get());
            surfaceproperty_list(pSurface.release());
            break;
          }
        case (TOKEN_DIFFERENCE):
          {
            // Trying to create a new difference of surfaces
            m_pLexer->match(TOKEN_DIFFERENCE);
            const string name = m_pLexer->match(TOKEN_NAME);
            CSurface *pSubSurface1 = getSurface(m_pLexer->match(TOKEN_NAME));
            CSurface *pSubSurface2 = getSurface(m_pLexer->match(TOKEN_NAME));
            auto_ptr<CBooleanSurface> pSurface(new CDifference(name,
                                                               pSubSurface1,
                                                               pSubSurface2));
            addSurface(pSurface.get());
            surfaceproperty_list(pSurface.release());
            break;
          }
        case (TOKEN_INTERSECTION):
          {
            // Trying to create a new intersection of surfaces
            m_pLexer->match(TOKEN_INTERSECTION);
            const string name = m_pLexer->match(TOKEN_NAME);
            CSurface *pSubSurface1 = getSurface(m_pLexer->match(TOKEN_NAME));
            CSurface *pSubSurface2 = getSurface(m_pLexer->match(TOKEN_NAME));
            auto_ptr<CBooleanSurface> pSurface(new CIntersection(name,
                                                                 pSubSurface1,
                                                                 pSubSurface2));
            addSurface(pSurface.get());
            surfaceproperty_list(pSurface.release());
            break;
          }
        default:
          {
            // new followed by something not good
            m_pLexer->error();
          }
        }

      // Resursive call for another statement
      statement_list(pCScene);
    }
  else if ( (nextToken == TOKEN_GENERAL)    ||
            (nextToken == TOKEN_STEREOGRAM) ||
            (nextToken == TOKEN_SHADER) )
    {
      // Setting properties for the scene in general (the "Stereogram"
      // and "Shader" tokens are just there so that the input files
      // can be cleaner, but for now general scene properties can be
      // set in them and in the "General" token).
      m_pLexer->match(nextToken);

      property_list(pCScene);

      statement_list(pCScene);
    }
  else if (nextToken == TOKEN_INSTANCE)
    {
      // Trying to instanciate an already described surface
      m_pLexer->match(TOKEN_INSTANCE);

      auto_ptr<CSurface> pSurface(instance(m_pLexer->match(TOKEN_NAME)));
      pCScene->addSurface(pSurface.get());
      property_list(pSurface.release());

      statement_list(pCScene);
    }
}

// The non-terminal symbol representing a list of properties appliable
// to surfaces, which are translating, scaling, rotating and the other
// properties
void CScanner::surfaceproperty_list(CSurface *pSurface)
{
  int nextToken = m_pLexer->lookahead();

  if (nextToken == TOKEN_TRANSLATE)
    {
      // Translation
      m_pLexer->match(TOKEN_TRANSLATE);
      pSurface->Translate(vector());
      surfaceproperty_list(pSurface);
    }
  else if (nextToken == TOKEN_SCALE)
    {
      // Scale
      m_pLexer->match(TOKEN_SCALE);
      pSurface->Scale(vector());
      surfaceproperty_list(pSurface);
    }
  else if (nextToken == TOKEN_ROTATE)
    {
      // Rotation
      m_pLexer->match(TOKEN_ROTATE);
      real_t angle = number() / MAKE_REAL(180.0) * PI;
      pSurface->Rotate(angle, vector());
      surfaceproperty_list(pSurface);
    }
  else
    {
      // Other properties
      property_list(pSurface);
    }
}

// The non-terminal symbol representing a list of properties appliable
// to any object (light, surface, scene in general)
void CScanner::property_list(CPropertyObject *pObject)
{
  int nextToken = m_pLexer->lookahead();

  if (nextToken == TOKEN_POSITION     ||
      nextToken == TOKEN_CAMERAPOS    ||
      nextToken == TOKEN_CAMERATARGET ||
      nextToken == TOKEN_UPVECTOR     ||
      nextToken == TOKEN_CENTER       ||
      nextToken == TOKEN_NORMAL       ||
      nextToken == TOKEN_QUADRATIC    ||
      nextToken == TOKEN_LINEAR       ||
      nextToken == TOKEN_MIX          ||
      nextToken == TOKEN_VERTEX0      ||
      nextToken == TOKEN_VERTEX1      ||
      nextToken == TOKEN_VERTEX2      )
    {
      // Vector properties
      string property = m_pLexer->match(nextToken);
      pObject->Assign(property, vector());
      property_list(pObject);
    }
  else if ( nextToken == TOKEN_LIGHTCOLOR   ||
            nextToken == TOKEN_SURFACECOLOR ||
            nextToken == TOKEN_BACKCOLOR    )
    {
      // Color properties
      string property = m_pLexer->match(nextToken);
      pObject->Assign(property, color());
      property_list(pObject);
    }
  else if ( nextToken == TOKEN_SMALLREAL        || 
            nextToken == TOKEN_INTENSITY        ||
            nextToken == TOKEN_AMBIENTCOEF      ||
            nextToken == TOKEN_DIFFUSECOEF      ||
            nextToken == TOKEN_SPECULARCOEF     ||
            nextToken == TOKEN_SHININESSCOEF    ||
            nextToken == TOKEN_REFLEXIONCOEF    ||
            nextToken == TOKEN_REFRACTIONCOEF   ||
            nextToken == TOKEN_REFRACTIONINDEX  ||
            nextToken == TOKEN_FOCALE           ||
            nextToken == TOKEN_FSTOP            ||
            nextToken == TOKEN_DIMFILM          ||
            nextToken == TOKEN_NEARCLIPPLANE    ||
            nextToken == TOKEN_FARCLIPPLANE     ||
            nextToken == TOKEN_GENREFRINDEX     ||
            nextToken == TOKEN_MIN_ENERGY       ||
            nextToken == TOKEN_RADIUS           ||
            nextToken == TOKEN_CONSTANT         ||
            nextToken == TOKEN_SIZE             ||
            nextToken == TOKEN_NEAREST_DEPTH    ||
            nextToken == TOKEN_FARTHEST_DEPTH   ||
            nextToken == TOKEN_NEAREST_MAPS_TO  ||
            nextToken == TOKEN_FARTHEST_MAPS_TO ||
            nextToken == TOKEN_RAY_OFFSET )
    {
      // Real properties
      string property = m_pLexer->match(nextToken);
      pObject->Assign(property, number());
      property_list(pObject);
    }
  else if ( nextToken == TOKEN_WIDTH                 ||
            nextToken == TOKEN_HEIGHT                ||
            nextToken == TOKEN_PRECISIONU            ||
            nextToken == TOKEN_PRECISIONV            ||
            nextToken == TOKEN_MAX_LEVEL             ||
            nextToken == TOKEN_RANDOM_PATTERN_WIDTH  ||
            nextToken == TOKEN_RANDOM_PATTERN_HEIGHT ||
            nextToken == TOKEN_REFLECTION_ENABLED    ||
            nextToken == TOKEN_REFRACTION_ENABLED    ||
            nextToken == TOKEN_SHADOW_TYPE )
    {
      // Integer properties
      string property = m_pLexer->match(nextToken);
      pObject->Assign(property, integer());
      property_list(pObject);
    }
  else if ( nextToken == TOKEN_PATTERN_FILE )
    {
      // String properties
      string property = m_pLexer->match(nextToken);
      pObject->Assign(property, m_pLexer->match(TOKEN_NAME));
      property_list(pObject);
    }
}

// The non-terminal symbol representing a color
CColor CScanner::color()
{
  real_t red   = number();
  real_t green = number();
  real_t blue  = number();
  return CColor(red, green, blue);
}
// The non-terminal symbol representing a vector
CVector CScanner::vector()
{
  real_t x = number();
  real_t y = number();
  real_t z = number();
  return CVector(x, y, z);
}
// The non-terminal symbol representing an integer
int CScanner::integer()
{
  return atoi(m_pLexer->match(TOKEN_INTEGER).c_str());
}
// The non-terminal symbol representing a number, real or integer
real_t CScanner::number()
{
  if (m_pLexer->lookahead() == TOKEN_INTEGER)
    return integer();
  else
    return (real_t) atof(m_pLexer->match(TOKEN_REAL).c_str());
}
