/******************************************************************************\
* LuaValue.cpp                                                                 *
* A class that somewhat mimics a Lua value.                                    *
*                                                                              *
*                                                                              *
* Copyright (C) 2005-2013 by Leandro Motta Barros.                             *
*                                                                              *
* Permission is hereby granted, free of charge, to any person obtaining a copy *
* of this software and associated documentation files (the "Software"), to     *
* deal in the Software without restriction, including without limitation the   *
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or  *
* sell copies of the Software, and to permit persons to whom the Software is   *
* furnished to do so, subject to the following conditions:                     *
*                                                                              *
* The above copyright notice and this permission notice shall be included in   *
* all copies or substantial portions of the Software.                          *
*                                                                              *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR   *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,     *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE *
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER       *
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING      *
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS *
* IN THE SOFTWARE.                                                             *
\******************************************************************************/

#include <cstring>
#include <Diluculum/LuaValue.hpp>
#include <Diluculum/LuaExceptions.hpp>


namespace Diluculum
{
   // - LuaValue::LuaValue -----------------------------------------------------
   LuaValue::LuaValue()
      : dataType_(LUA_TNIL)
   { }


   LuaValue::LuaValue (bool b)
      : dataType_(LUA_TBOOLEAN)
   {
      data_.typeBool = b;
   }


   LuaValue::LuaValue (float n)
      : dataType_(LUA_TNUMBER)
   {
      lua_Number num = static_cast<lua_Number>(n);
      data_.typeNumber = num;
   }


   LuaValue::LuaValue (double n)
      : dataType_(LUA_TNUMBER)
   {
      lua_Number num = static_cast<lua_Number>(n);
      data_.typeNumber = num;
   }


   LuaValue::LuaValue (long double n)
      : dataType_(LUA_TNUMBER)
   {
      lua_Number num = static_cast<lua_Number>(n);
      data_.typeNumber = num;
   }


   LuaValue::LuaValue (short n)
      : dataType_(LUA_TNUMBER)
   {
      lua_Number num = static_cast<lua_Number>(n);
      data_.typeNumber = num;
   }


   LuaValue::LuaValue (unsigned short n)
      : dataType_(LUA_TNUMBER)
   {
      lua_Number num = static_cast<lua_Number>(n);
      data_.typeNumber = num;
   }


   LuaValue::LuaValue (int n)
      : dataType_(LUA_TNUMBER)
   {
      lua_Number num = static_cast<lua_Number>(n);
      data_.typeNumber = num;
   }


   LuaValue::LuaValue (unsigned n)
      : dataType_(LUA_TNUMBER)
   {
      lua_Number num = static_cast<lua_Number>(n);
      data_.typeNumber = num;
   }


   LuaValue::LuaValue (long n)
      : dataType_(LUA_TNUMBER)
   {
      lua_Number num = static_cast<lua_Number>(n);
      data_.typeNumber = num;
   }


   LuaValue::LuaValue (unsigned long n)
      : dataType_(LUA_TNUMBER)
   {
      lua_Number num = static_cast<lua_Number>(n);
      data_.typeNumber = num;
   }


   LuaValue::LuaValue (const std::string& s)
      : dataType_(LUA_TSTRING)
   {
      new(&data_.typeString) std::string(s);
   }


   LuaValue::LuaValue (const char* s)
      : dataType_(LUA_TSTRING)
   {
      new(&data_.typeString) std::string(s);
   }


   LuaValue::LuaValue (const LuaValueMap& t)
      : dataType_(LUA_TTABLE)
   {
      new(&data_.typeLuaValueMap) LuaValueMap(t);
   }


   LuaValue::LuaValue (lua_CFunction f)
      : dataType_(LUA_TFUNCTION)
   {
      new(&data_.typeFunction) LuaFunction(f);
   }


   LuaValue::LuaValue (const LuaFunction& f)
      : dataType_(LUA_TFUNCTION)
   {
      new(&data_.typeFunction) LuaFunction(f);
   }


   LuaValue::LuaValue (const LuaUserData& ud)
      : dataType_(LUA_TUSERDATA)
   {
      new(&data_.typeUserData) LuaUserData(ud);
   }


   LuaValue::LuaValue (const LuaValueList& v)
      // Avoids possible memory corruption during destroyObjectAtData
      : dataType_(LUA_TNIL)
   {
      if (v.size() >= 1)
         *this = v[0];
      else
         *this = Nil;
   }


   LuaValue::LuaValue (const LuaValue& other)
      : dataType_ (other.dataType_)
   {
      switch (dataType_)
      {
         case LUA_TSTRING:
            new(&data_.typeString) std::string(other.asString());
            break;

         case LUA_TTABLE:
            new(&data_.typeLuaValueMap) LuaValueMap (other.asTable());
            break;

         case LUA_TUSERDATA:
            new(&data_.typeUserData) LuaUserData (other.asUserData());
            break;

         case LUA_TFUNCTION:
            new(&data_.typeFunction) LuaFunction (other.asFunction());
            break;

         default:
            // no constructor needed.
            //memcpy (&data_, &other.data_, sizeof(PossibleTypes));
            data_ = other.data_;
             break;
      }
   }



   // - LuaValue::operator= ----------------------------------------------------
   LuaValue& LuaValue::operator= (const LuaValue& rhs)
   {
      destroyObjectAtData();

      dataType_ = rhs.dataType_;

      switch (dataType_)
      {
         case LUA_TSTRING:
            new(&data_.typeString) std::string (rhs.asString());
            break;

         case LUA_TTABLE:
            new(&data_.typeLuaValueMap) LuaValueMap (rhs.asTable());
            break;

         case LUA_TUSERDATA:
            new(&data_.typeUserData) LuaUserData (rhs.asUserData());
            break;

         case LUA_TFUNCTION:
            new(&data_.typeFunction) LuaFunction (rhs.asFunction());
            break;

         default:
            // no constructor needed.
            //memcpy (&data_, &rhs.data_, sizeof(PossibleTypes));
            data_ = rhs.data_;
             break;
      }

      return *this;
   }


   const LuaValueList& LuaValue::operator= (const LuaValueList& rhs)
   {
      if (rhs.size() >= 1)
         *this = rhs[0];
      else
         *this = Nil;

      return rhs;
   }



   // - LuaValue::typeName -----------------------------------------------------
   std::string LuaValue::typeName() const
   {
      switch (dataType_)
      {
         case LUA_TNIL:
            return "nil";

         case LUA_TBOOLEAN:
            return "boolean";

         case LUA_TNUMBER:
            return "number";

         case LUA_TSTRING:
            return "string";

         case LUA_TTABLE:
            return "table";

         case LUA_TFUNCTION:
            return "function";

         case LUA_TUSERDATA:
            return "userdata";

         default: // can't happen
            assert (false
                    && "Invalid type found in a call to 'LuaValue::typeName()'.");
            return ""; // return something to make compilers happy.
      }
   }



   // - LuaValue::asNumber() ---------------------------------------------------
   lua_Number LuaValue::asNumber() const
   {
      if (dataType_ == LUA_TNUMBER)
      {
         return data_.typeNumber;
      }
      else
      {
         throw TypeMismatchError ("number", typeName());
      }
   }



   // - LuaValue::asInteger() --------------------------------------------------
   lua_Integer LuaValue::asInteger() const
   {
      if (dataType_ == LUA_TNUMBER)
      {
         return static_cast<lua_Integer>(data_.typeNumber);
      }
      else
      {
         throw TypeMismatchError ("number", typeName());
      }
   }



   // - LuaValue::asString -----------------------------------------------------
   const std::string& LuaValue::asString() const
   {
      if (dataType_ == LUA_TSTRING)
      {
         return data_.typeString;
      }
      else
      {
         throw TypeMismatchError ("string", typeName());
      }
   }



   // - LuaValue::asBoolean ----------------------------------------------------
   bool LuaValue::asBoolean() const
   {
      if (dataType_ == LUA_TBOOLEAN)
      {
         return data_.typeBool;
      }
      else
      {
         throw TypeMismatchError ("boolean", typeName());
      }
   }



   // - LuaValue::asTable ------------------------------------------------------
   LuaValueMap LuaValue::asTable() const
   {
      if (dataType_ == LUA_TTABLE)
      {
         return data_.typeLuaValueMap;
      }
      else
      {
         throw TypeMismatchError ("table", typeName());
      }
   }



   // - LuaValue::asFunction ---------------------------------------------------
   const LuaFunction& LuaValue::asFunction() const
   {
      if (dataType_ == LUA_TFUNCTION)
      {
         return data_.typeFunction;
      }
      else
      {
         throw TypeMismatchError ("function", typeName());
      }
   }



   // - LuaValue::asUserData ---------------------------------------------------
   const LuaUserData& LuaValue::asUserData() const
   {
      if (dataType_ == LUA_TUSERDATA)
      {
         return data_.typeUserData;
      }
      else
      {
         throw TypeMismatchError ("userdata", typeName());
      }
   }

   LuaUserData& LuaValue::asUserData()
   {
      if (dataType_ == LUA_TUSERDATA)
      {
         return data_.typeUserData;
      }
      else
      {
         throw TypeMismatchError ("userdata", typeName());
      }
   }



   // - LuaValue::operator< ----------------------------------------------------
   bool LuaValue::operator< (const LuaValue& rhs) const
   {
      std::string lhsTypeName = typeName();
      std::string rhsTypeName = rhs.typeName();

      if (lhsTypeName < rhsTypeName)
         return true;
      else if (lhsTypeName > rhsTypeName)
         return false;
      else // lhsTypeName == rhsTypeName
      {
         if (lhsTypeName == "nil")
            return false;
         else if (lhsTypeName == "boolean")
            return asBoolean() < rhs.asBoolean();
         else if (lhsTypeName == "number")
            return asNumber() < rhs.asNumber();
         else if (lhsTypeName == "string")
            return asString() < rhs.asString();
         else if (lhsTypeName == "function")
            return asFunction() < rhs.asFunction();
         else if (lhsTypeName == "userdata")
            return asUserData() < rhs.asUserData();
         else if (lhsTypeName == "table")
         {
            const LuaValueMap lhsMap = asTable();
            const LuaValueMap rhsMap = rhs.asTable();

            if (lhsMap.size() < rhsMap.size())
               return true;
            else if (lhsMap.size() > rhsMap.size())
               return false;
            else // lhsMap.size() == rhsMap.size()
            {
               typedef LuaValueMap::const_iterator iter_t;

               iter_t pLHS = lhsMap.begin();
               iter_t pRHS = rhsMap.begin();
               const iter_t end = lhsMap.end();

               while (pLHS != end)
               {
                  // check the key first
                  if (pLHS->first < pRHS->first)
                     return true;
                  else if (pLHS->first > pRHS->first)
                     return false;

                  // then check the value
                  if (pLHS->second < pRHS->second)
                     return true;
                  else if (pLHS->second > pRHS->second)
                     return false;

                  // make the iterators iterate
                  ++pRHS;
                  ++pLHS;
               }
               return false;
            }
         }
         else
         {
            assert (false && "Unsupported type found at a call "
                    "to 'LuaValue::operator<()'");
            return false; // make the compiler happy.
         }
      }
   }



   // - LuaValue::operator> ----------------------------------------------------
   bool LuaValue::operator> (const LuaValue& rhs) const
   {
      std::string lhsTypeName = typeName();
      std::string rhsTypeName = rhs.typeName();

      if (lhsTypeName > rhsTypeName)
         return true;
      else if (lhsTypeName < rhsTypeName)
         return false;
      else // lhsTypeName == rhsTypeName
      {
         if (lhsTypeName == "nil")
            return false;
         else if (lhsTypeName == "boolean")
            return asBoolean() > rhs.asBoolean();
         else if (lhsTypeName == "number")
            return asNumber() > rhs.asNumber();
         else if (lhsTypeName == "string")
            return asString() > rhs.asString();
         else if (lhsTypeName == "function")
            return asFunction() > rhs.asFunction();
         else if (lhsTypeName == "userdata")
            return asUserData() > rhs.asUserData();
         else if (lhsTypeName == "table")
         {
            const LuaValueMap lhsMap = asTable();
            const LuaValueMap rhsMap = rhs.asTable();

            if (lhsMap.size() > rhsMap.size())
               return true;
            else if (lhsMap.size() < rhsMap.size())
               return false;
            else // lhsMap.size() == rhsMap.size()
            {
               typedef LuaValueMap::const_iterator iter_t;

               iter_t pLHS = lhsMap.begin();
               iter_t pRHS = rhsMap.begin();
               const iter_t end = lhsMap.end();

               while (pLHS != end)
               {
                  // check the key first
                  if (pLHS->first > pRHS->first)
                     return true;
                  else if (pLHS->first < pRHS->first)
                     return false;

                  // then check the value
                  if (pLHS->second > pRHS->second)
                     return true;
                  else if (pLHS->second < pRHS->second)
                     return false;

                  // make the iterators iterate
                  ++pRHS;
                  ++pLHS;
               }
               return false;
            }
         }
         else
         {
            assert (false && "Unsupported type found at a call "
                    "to 'LuaValue::operator>()'");
            return false; // make the compiler happy.
         }
      }
   }



   // - LuaValue::operator== ---------------------------------------------------
   bool LuaValue::operator== (const LuaValue& rhs) const
   {
      std::string lhsTypeName = typeName();
      std::string rhsTypeName = rhs.typeName();

      if (typeName() != rhs.typeName())
         return false;
      else switch (type())
      {
         case LUA_TNIL:
            return true;

         case LUA_TBOOLEAN:
            return asBoolean() == rhs.asBoolean();

         case LUA_TNUMBER:
            return asNumber() == rhs.asNumber();

         case LUA_TSTRING:
            return asString() == rhs.asString();

         case LUA_TTABLE:
            return asTable() == rhs.asTable();

         case LUA_TFUNCTION:
            return asFunction() == rhs.asFunction();

         case LUA_TUSERDATA:
            return asUserData() == rhs.asUserData();

         default:
         {
            assert(
               false
               && "Invalid type found in a call to 'LuaValue::operator==()'.");
            return 0; // make compilers happy
         }
      }
   }



   // - LuaValue::operator[] ---------------------------------------------------
   LuaValue& LuaValue::operator[] (const LuaValue& key)
   {
      if (type() != LUA_TTABLE)
         throw TypeMismatchError ("table", typeName());

      LuaValueMap* pTable = &data_.typeLuaValueMap;

      return (*pTable)[key];
   }



   const LuaValue& LuaValue::operator[] (const LuaValue& key) const
   {
      if (type() != LUA_TTABLE)
         throw TypeMismatchError ("table", typeName());

      const LuaValueMap* pTable = &data_.typeLuaValueMap;

      LuaValueMap::const_iterator it = (*pTable).find(key);

      if (it == (*pTable).end())
         return Nil;

      return it->second;
   }



   // - LuaValue::destroyObjectAtData ------------------------------------------
   void LuaValue::destroyObjectAtData()
   {
      switch (dataType_)
      {
         case LUA_TSTRING:
         {
            std::string* ps = &data_.typeString;
            ps->~basic_string();
            break;
         }

         case LUA_TTABLE:
         {
            LuaValueMap* pm = &data_.typeLuaValueMap;
            pm->~LuaValueMap();
            break;
         }

         case LUA_TUSERDATA:
         {
            LuaUserData* pd = &data_.typeUserData;
            pd->~LuaUserData();
            break;
         }

         case LUA_TFUNCTION:
         {
            LuaFunction* pf = &data_.typeFunction;
            pf->~LuaFunction();
            break;
         }

         default:
            // no destructor needed.
            break;
      }
   }

} // namespace Diluculum
