/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-10 by Raw Material Software Ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the GNU General
   Public License (Version 2), as published by the Free Software Foundation.
   A copy of the license is included in the JUCE distribution, or can be found
   online at www.gnu.org/licenses.

   JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

  ------------------------------------------------------------------------------

   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.rawmaterialsoftware.com/juce for more information.

  ==============================================================================
*/

#include "../core/juce_StandardHeader.h"

BEGIN_JUCE_NAMESPACE

#include "juce_Variant.h"
#include "juce_DynamicObject.h"

//==============================================================================
var::var() throw()
   : type (voidType)
{
    value.doubleValue = 0;
}

var::~var() throw()
{
    if (type == stringType)
        delete value.stringValue;
    else if (type == objectType && value.objectValue != 0)
        value.objectValue->decReferenceCount();
}

const var var::null;

//==============================================================================
var::var (const var& valueToCopy)
   : type (valueToCopy.type),
     value (valueToCopy.value)
{
    if (type == stringType)
        value.stringValue = new String (*(value.stringValue));
    else if (type == objectType && value.objectValue != 0)
        value.objectValue->incReferenceCount();
}

var::var (const int value_) throw()
   : type (intType)
{
    value.intValue = value_;
}

var::var (const bool value_) throw()
   : type (boolType)
{
    value.boolValue = value_;
}

var::var (const double value_) throw()
   : type (doubleType)
{
    value.doubleValue = value_;
}

var::var (const String& value_)
   : type (stringType)
{
    value.stringValue = new String (value_);
}

var::var (const char* const value_)
   : type (stringType)
{
    value.stringValue = new String (value_);
}

var::var (const juce_wchar* const value_)
   : type (stringType)
{
    value.stringValue = new String (value_);
}

var::var (DynamicObject* const object)
   : type (objectType)
{
    value.objectValue = object;

    if (object != 0)
        object->incReferenceCount();
}

var::var (MethodFunction method_) throw()
   : type (methodType)
{
    value.methodValue = method_;
}

//==============================================================================
void var::swapWith (var& other) throw()
{
    swapVariables (type, other.type);
    swapVariables (value, other.value);
}

var& var::operator= (const var& value_)         { var newValue (value_); swapWith (newValue); return *this; }
var& var::operator= (int value_)                { var newValue (value_); swapWith (newValue); return *this; }
var& var::operator= (bool value_)               { var newValue (value_); swapWith (newValue); return *this; }
var& var::operator= (double value_)             { var newValue (value_); swapWith (newValue); return *this; }
var& var::operator= (const char* value_)        { var newValue (value_); swapWith (newValue); return *this; }
var& var::operator= (const juce_wchar* value_)  { var newValue (value_); swapWith (newValue); return *this; }
var& var::operator= (const String& value_)      { var newValue (value_); swapWith (newValue); return *this; }
var& var::operator= (DynamicObject* value_)     { var newValue (value_); swapWith (newValue); return *this; }
var& var::operator= (MethodFunction value_)     { var newValue (value_); swapWith (newValue); return *this; }

//==============================================================================
var::operator int() const
{
    switch (type)
    {
        case voidType:      break;
        case intType:       return value.intValue;
        case boolType:      return value.boolValue ? 1 : 0;
        case doubleType:    return static_cast <int> (value.doubleValue);
        case stringType:    return value.stringValue->getIntValue();
        case objectType:    break;
        default:            jassertfalse; break;
    }

    return 0;
}

var::operator bool() const
{
    switch (type)
    {
        case voidType:      break;
        case intType:       return value.intValue != 0;
        case boolType:      return value.boolValue;
        case doubleType:    return value.doubleValue != 0;
        case stringType:    return value.stringValue->getIntValue() != 0
                                    || value.stringValue->trim().equalsIgnoreCase ("true")
                                    || value.stringValue->trim().equalsIgnoreCase ("yes");
        case objectType:    return value.objectValue != 0;
        default:            jassertfalse; break;
    }

    return false;
}

var::operator float() const
{
    return (float) operator double();
}

var::operator double() const
{
    switch (type)
    {
        case voidType:      break;
        case intType:       return value.intValue;
        case boolType:      return value.boolValue ? 1.0 : 0.0;
        case doubleType:    return value.doubleValue;
        case stringType:    return value.stringValue->getDoubleValue();
        case objectType:    break;
        default:            jassertfalse; break;
    }

    return 0.0;
}

const String var::toString() const
{
    switch (type)
    {
        case voidType:      return String::empty;
        case intType:       return String (value.intValue);
        case boolType:      return String::charToString (value.boolValue ? '1' : '0');
        case doubleType:    return String (value.doubleValue);
        case stringType:    return *(value.stringValue);
        case objectType:    return "Object 0x" + String::toHexString ((int) (pointer_sized_int) value.objectValue);
        case methodType:    return "Method";
        default:            jassertfalse; break;
    }

    return String::empty;
}

var::operator const String() const
{
    return toString();
}

DynamicObject* var::getObject() const
{
    return type == objectType ? value.objectValue : 0;
}

//==============================================================================
bool var::equals (const var& other) const throw()
{
    switch (type)
    {
        case voidType:      return other.isVoid();
        case intType:       return value.intValue == static_cast <int> (other);
        case boolType:      return value.boolValue == static_cast <bool> (other);
        case doubleType:    return value.doubleValue == static_cast <double> (other);
        case stringType:    return (*(value.stringValue)) == other.toString();
        case objectType:    return value.objectValue == other.getObject();
        case methodType:    return value.methodValue == other.value.methodValue && other.isMethod();
        default:            jassertfalse; break;
    }

    return false;
}

bool operator== (const var& v1, const var& v2) throw()      { return v1.equals (v2); }
bool operator!= (const var& v1, const var& v2) throw()      { return ! v1.equals (v2); }
bool operator== (const var& v1, const String& v2) throw()   { return v1.toString() == v2; }
bool operator!= (const var& v1, const String& v2) throw()   { return v1.toString() != v2; }

//==============================================================================
void var::writeToStream (OutputStream& output) const
{
    switch (type)
    {
        case voidType:      output.writeCompressedInt (0); break;
        case intType:       output.writeCompressedInt (5); output.writeByte (1); output.writeInt (value.intValue); break;
        case boolType:      output.writeCompressedInt (1); output.writeByte (value.boolValue ? 2 : 3); break;
        case doubleType:    output.writeCompressedInt (9); output.writeByte (4); output.writeDouble (value.doubleValue); break;
        case stringType:
        {
            const int len = value.stringValue->getNumBytesAsUTF8() + 1;
            output.writeCompressedInt (len + 1);
            output.writeByte (5);
            HeapBlock<char> temp (len);
            value.stringValue->copyToUTF8 (temp, len);
            output.write (temp, len);
            break;
        }
        case objectType:
        case methodType:    output.writeCompressedInt (0); jassertfalse; break; // Can't write an object to a stream!
        default:            jassertfalse; break; // Is this a corrupted object?
    }
}

const var var::readFromStream (InputStream& input)
{
    const int numBytes = input.readCompressedInt();

    if (numBytes > 0)
    {
        switch (input.readByte())
        {
            case 1:     return var (input.readInt());
            case 2:     return var (true);
            case 3:     return var (false);
            case 4:     return var (input.readDouble());
            case 5:
            {
                MemoryBlock mb;
                input.readIntoMemoryBlock (mb, numBytes - 1);
                return var (String::fromUTF8 (static_cast <const char*> (mb.getData()), (int) mb.getSize()));
            }

            default:    input.skipNextBytes (numBytes - 1); break;
        }
    }

    return var::null;
}

const var var::operator[] (const var::identifier& propertyName) const
{
    if (type == objectType && value.objectValue != 0)
        return value.objectValue->getProperty (propertyName);

    return var::null;
}

const var var::invoke (const var::identifier& method, const var* arguments, int numArguments) const
{
    if (type == objectType && value.objectValue != 0)
        return value.objectValue->invokeMethod (method, arguments, numArguments);

    return var::null;
}

const var var::invoke (const var& targetObject, const var* arguments, int numArguments) const
{
    if (isMethod())
    {
        DynamicObject* const target = targetObject.getObject();

        if (target != 0)
            return (target->*(value.methodValue)) (arguments, numArguments);
    }

    return var::null;
}

const var var::call (const var::identifier& method) const
{
    return invoke (method, 0, 0);
}

const var var::call (const var::identifier& method, const var& arg1) const
{
    return invoke (method, &arg1, 1);
}

const var var::call (const var::identifier& method, const var& arg1, const var& arg2) const
{
    var args[] = { arg1, arg2 };
    return invoke (method, args, 2);
}

const var var::call (const var::identifier& method, const var& arg1, const var& arg2, const var& arg3)
{
    var args[] = { arg1, arg2, arg3 };
    return invoke (method, args, 3);
}

const var var::call (const var::identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4) const
{
    var args[] = { arg1, arg2, arg3, arg4 };
    return invoke (method, args, 4);
}

const var var::call (const var::identifier& method, const var& arg1, const var& arg2, const var& arg3, const var& arg4, const var& arg5) const
{
    var args[] = { arg1, arg2, arg3, arg4, arg5 };
    return invoke (method, args, 5);
}


//==============================================================================
var::identifier::identifier() throw()
    : hashCode (0)
{
}

var::identifier::identifier (const String& name_)
    : name (name_),
      hashCode (name_.hashCode())
{
    /* An identifier string must be suitable for use as a script variable or XML
       attribute, so it can only contain this limited set of characters.. */
    jassert (name.containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_") && name.isNotEmpty());
}

var::identifier::identifier (const char* const name_)
    : name (name_),
      hashCode (name.hashCode())
{
    /* An identifier string must be suitable for use as a script variable or XML
       attribute, so it can only contain this limited set of characters.. */
    jassert (name.containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_") && name.isNotEmpty());
}

var::identifier::~identifier()
{
}


END_JUCE_NAMESPACE
