// ----------------------------------------------------------------------------
//
// flxmlrpc Copyright (c) 2015 by W1HKJ, Dave Freese <iam_w1hkj@w1hkj.com>
//    
// XmlRpc++ Copyright (c) 2002-2008 by Chris Morley
//
// This file is part of fldigi
//
// flxmlrpc is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------

#ifndef _XMLRPCVALUE_H_
#define _XMLRPCVALUE_H_

#if defined(_MSC_VER)
# pragma warning(disable:4786)    // identifier was truncated in debug info
#endif

#include <map>
#include <string>
#include <vector>
#include <time.h>

namespace XmlRpc {

  enum xmlrpc_nil_t { nil };

  //! A class to represent RPC arguments and results.
  //! Each XmlRpcValue object contains a typed value,
  //! where the type is determined by the initial value
  //! assigned to the object.
  //   should probably refcount them...
  class XmlRpcValue {
  public:

    //! XmlRpcValue types
    enum Type {
      TypeInvalid,
      TypeNil,
      TypeBoolean,
      TypeInt,
      TypeUnsigned,
      TypeLongLong,
      TypeDouble,
      TypeString,
      TypeDateTime,
      TypeBase64,
      TypeArray,
      TypeStruct
    };

    // Non-primitive types
    typedef std::vector<unsigned char> BinaryData;
    typedef std::vector<XmlRpcValue> ValueArray;
    typedef std::map<std::string, XmlRpcValue> ValueStruct;


    // Constructors
    //! Construct an empty XmlRpcValue
    XmlRpcValue() : _type(TypeInvalid) { _value.asBinary = 0; }

    //! Construct an XmlRpcValue with a nil value
    XmlRpcValue(xmlrpc_nil_t value) : _type(TypeNil) { }

    //! Construct an XmlRpcValue with a bool value
    XmlRpcValue(bool value) : _type(TypeBoolean) { _value.asBool = value; }

    //! Construct an XmlRpcValue with an int value
    XmlRpcValue(int value)  : _type(TypeInt) { _value.asInt = value; }

    XmlRpcValue(unsigned int value)  : _type(TypeUnsigned) { _value.asUnsigned = value; }

    XmlRpcValue(long long value)  : _type(TypeLongLong) { _value.asLongLong = value; }
 
    //! Construct an XmlRpcValue with a double value
    XmlRpcValue(double value)  : _type(TypeDouble) { _value.asDouble = value; }

    //! Construct an XmlRpcValue with a string value
    XmlRpcValue(std::string const& value) : _type(TypeString) 
    { _value.asString = new std::string(value); }

    //! Construct an XmlRpcValue with a string value.
    //! @param value A null-terminated (C) string.
    XmlRpcValue(const char* value)  : _type(TypeString)
    { _value.asString = new std::string(value); }

    XmlRpcValue(BinaryData const& value) : _type(TypeBase64) 
    { _value.asBinary = new BinaryData(value); }

    XmlRpcValue(ValueStruct const& value) : _type(TypeStruct) 
    { _value.asStruct = new ValueStruct(value); }

    XmlRpcValue(ValueArray const& value) : _type(TypeArray) 
    { _value.asArray = new ValueArray(value); }

    //! Construct an XmlRpcValue with a date/time value.
    //! @param value A pointer to a struct tm (see localtime)
    XmlRpcValue(struct tm* value)  : _type(TypeDateTime) 
    { _value.asTime = new struct tm(*value); }

    //! Construct an XmlRpcValue with a binary data value
    //! @param value A pointer to data
    //! @param nBytes The length of the data pointed to, in bytes
    XmlRpcValue(void* value, int nBytes)  : _type(TypeBase64)
    {
      _value.asBinary = new BinaryData((char*)value, ((char*)value)+nBytes);
    }

    //! Construct from xml, beginning at *offset chars into the string, updates offset
    XmlRpcValue(std::string const& xml, int* offset) : _type(TypeInvalid)
    { if ( ! fromXml(xml,offset)) _type = TypeInvalid; }

    //! Copy constructor
    XmlRpcValue(XmlRpcValue const& rhs) : _type(TypeInvalid) { *this = rhs; }

    //! Destructor (make virtual if you want to subclass)
    /*virtual*/ ~XmlRpcValue() { invalidate(); }

    //! Erase the current value
    void clear() { invalidate(); }

    // Operators
    //! Assignment from one XmlRpcValue to this one.
    //! @param rhs The value in rhs is copied to this value.
    XmlRpcValue& operator=(XmlRpcValue const& rhs);

    //! Assign nil to this XmlRpcValue.
    XmlRpcValue& operator=(xmlrpc_nil_t const& rhs) { return operator=(XmlRpcValue(rhs)); }

    //! Assign a bool to this XmlRpcValue.
    XmlRpcValue& operator=(bool const& rhs) { return operator=(XmlRpcValue(rhs)); }

    //! Assign an int to this XmlRpcValue.
    XmlRpcValue& operator=(int const& rhs) { return operator=(XmlRpcValue(rhs)); }

    //! Assign a double to this XmlRpcValue.
    XmlRpcValue& operator=(double const& rhs) { return operator=(XmlRpcValue(rhs)); }

    //! Assign a string to this XmlRpcValue.
    XmlRpcValue& operator=(const char* rhs) { return operator=(XmlRpcValue(std::string(rhs))); }

    //! Tests two XmlRpcValues for equality
    bool operator==(XmlRpcValue const& other) const;

    //! Tests two XmlRpcValues for inequality
    bool operator!=(XmlRpcValue const& other) const;

    //! Treat an XmlRpcValue as a bool.
    //! Throws XmlRpcException if the value is initialized to 
    //! a type that is not TypeBoolean.
    operator bool&()          { assertType(TypeBoolean); return _value.asBool; }
    operator bool() const     { assertType(TypeBoolean); return _value.asBool; }

    //! Treat an XmlRpcValue as an int.
    //! Throws XmlRpcException if the value is initialized to 
    //! a type that is not TypeInt.
    operator int&()           { assertType(TypeInt); return _value.asInt; }
    operator int() const      { assertType(TypeInt); return _value.asInt; }

    operator unsigned int&()           { assertType(TypeUnsigned); return _value.asUnsigned; }
    operator unsigned int() const      { assertType(TypeUnsigned); return _value.asUnsigned; }

    operator long long&()           { assertType(TypeLongLong); return _value.asLongLong; }
    operator long long() const      { assertType(TypeLongLong); return _value.asLongLong; }

    //! Treat an XmlRpcValue as a double.
    //! Throws XmlRpcException if the value is initialized to 
    //! a type that is not TypeDouble.
    operator double&()        { assertType(TypeDouble); return _value.asDouble; }
    operator double() const   { assertType(TypeDouble); return _value.asDouble; }

    //! Treat an XmlRpcValue as a string.
    //! Throws XmlRpcException if the value is initialized to 
    //! a type that is not TypeString.
    operator std::string&()             { assertType(TypeString); return *_value.asString; }
    operator std::string const&() const { assertType(TypeString); return *_value.asString; }

    //! Access the BinaryData value.
    //! Throws XmlRpcException if the value is initialized to 
    //! a type that is not TypeBase64.
    operator BinaryData&()              { assertType(TypeBase64); return *_value.asBinary; }
    operator BinaryData const&() const  { assertType(TypeBase64); return *_value.asBinary; }

    //! Access the DateTime value.
    //! Throws XmlRpcException if the value is initialized to 
    //! a type that is not TypeDateTime.
    operator struct tm&()               { assertType(TypeDateTime); return *_value.asTime; }
    operator struct tm const&() const   { assertType(TypeDateTime); return *_value.asTime; }


    //! Const array value accessor.
    //! Access the ith value of the array.
    //! Throws XmlRpcException if the value is not an array or if the index i is
    //! not a valid index for the array.
    XmlRpcValue const& operator[](int i) const { assertArray(i+1); return _value.asArray->at(i); }

    //! Array value accessor.
    //! Access the ith value of the array, growing the array if necessary.
    //! Throws XmlRpcException if the value is not an array.
    XmlRpcValue& operator[](int i)             { assertArray(i+1); return _value.asArray->at(i); }

    //! Struct entry accessor.
    //! Returns the value associated with the given entry, creating one if necessary.
    XmlRpcValue& operator[](std::string const& k) { assertStruct(); return (*_value.asStruct)[k]; }

    //! Struct entry accessor.
    //! Returns the value associated with the given entry, creating one if necessary.
    XmlRpcValue& operator[](const char* k) { assertStruct(); std::string s(k); return (*_value.asStruct)[s]; }

    //! Access the struct value map.
    //! Can be used to iterate over the entries in the map to find all defined entries.
    operator ValueStruct const&() { assertStruct(); return *_value.asStruct; } 

    operator ValueArray const&() const { assertType(TypeArray); return *_value.asArray; } 

    // Accessors
    //! Return true if the value has been set to something.
    bool valid() const { return _type != TypeInvalid; }

    //! Return the type of the value stored. \see Type.
    Type const &getType() const { return _type; }

    //! Return the size for string, base64, array, and struct values.
    int size() const;

    //! Specify the size for array values. Array values will grow beyond this size if needed.
    void setSize(int size)    { assertArray(size); }

    //! Check for the existence of a struct member by name.
    bool hasMember(const std::string& name) const;

    //! Decode xml. Destroys any existing value.
    bool fromXml(std::string const& valueXml, int* offset);

    //! Encode the Value in xml
    std::string toXml() const;

    //! Write the value (no xml encoding)
    std::ostream& write(std::ostream& os) const;

    // Formatting
    //! Return the format used to write double values.
    static std::string const& getDoubleFormat() { return _doubleFormat; }

    //! Specify the format used to write double values.
    static void setDoubleFormat(const char* f) { _doubleFormat = f; }


  protected:
    // Clean up
    void invalidate();

    // Type checking. Non-const versions coerce to the desired type if currently un-typed.
    void assertType(Type t) const;
    void assertType(Type t);
    void assertArray(int size) const;
    void assertArray(int size);
    void assertStruct();

    // XML decoding
    bool boolFromXml(std::string const& valueXml, int* offset);
    bool intFromXml(std::string const& valueXml, int* offset);
    bool doubleFromXml(std::string const& valueXml, int* offset);
    bool stringFromXml(std::string const& valueXml, int* offset);
    bool timeFromXml(std::string const& valueXml, int* offset);
    bool binaryFromXml(std::string const& valueXml, int* offset);
    bool arrayFromXml(std::string const& valueXml, int* offset);
    bool structFromXml(std::string const& valueXml, int* offset);

    // XML encoding
    std::string nilToXml() const;
    std::string boolToXml() const;
    std::string intToXml() const;
    std::string doubleToXml() const;
    std::string stringToXml() const;
    std::string timeToXml() const;
    std::string binaryToXml() const;
    std::string arrayToXml() const;
    std::string structToXml() const;

    // Format strings
    static std::string _doubleFormat;

    // Type tag and values
    Type _type;

    // At some point I will split off Arrays and Structs into
    // separate ref-counted objects for more efficient copying.
    union {
      bool          asBool;
      int           asInt;
      unsigned int  asUnsigned;
      long long     asLongLong;
      double        asDouble;
      struct tm*    asTime;
      std::string*  asString;
      BinaryData*   asBinary;
      ValueArray*   asArray;
      ValueStruct*  asStruct;
    } _value;
    
  };
} // namespace XmlRpc


std::ostream& operator<<(std::ostream& os, XmlRpc::XmlRpcValue& v);


#endif // _XMLRPCVALUE_H_
