/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield 
 *
 * This library is open source and may be redistributed and/or modified under  
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 * 
 * This library 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 
 * OpenSceneGraph Public License for more details.
*/
//osgIntrospection - Copyright (C) 2005 Marco Jez

#ifndef OSGINTROSPECTION_READERWRITER_
#define OSGINTROSPECTION_READERWRITER_

#include <osgIntrospection/Value>
#include <osgIntrospection/Type>
#include <osgIntrospection/Exceptions>
#include <osgIntrospection/variant_cast>

#include <osg/Vec2>
#include <osg/Vec3>
#include <osg/Vec4>
#include <osg/ref_ptr>

#include <iostream>
#include <sstream>


namespace osgIntrospection
{

    /// This is the base class for reader/writer objects. A ReaderWriter's
    /// purpose is to provide the means for writing the content of a Value
    /// object to a stream and for reading it back. Descendants can either
    /// be specialized for just one data type or they can handle several
    /// types, that's up to the implementor. A derived class is not required
    /// to support all streaming operations (text write, text read, bin write
    /// and bin read), it can implement just some of them, although full
    /// support is strongly encouraged.
    class ReaderWriter
    {
    public:
        class Options
        {
        public:
            Options(): fno_(false) {}
            virtual ~Options() {}

            bool getForceNumericOutput() const    { return fno_; }
            void setForceNumericOutput(bool fno)  { fno_ = fno; }

        private:
            bool fno_;
        };

        /// Writes a textual representation of the value's content to a stream.
        virtual std::ostream &writeTextValue(std::ostream &, const Value& v, const Options* = 0) const { throw StreamingNotSupportedException(StreamingNotSupportedException::TEXT_WRITE, v.getType().getExtendedTypeInfo()); }

        /// Reads a textual representation of the value's content from a stream.
        virtual std::istream &readTextValue(std::istream &, Value& v, const Options* = 0) const { throw StreamingNotSupportedException(StreamingNotSupportedException::TEXT_READ, v.getType().getExtendedTypeInfo()); }

        /// Writes a textual representation of the value's content to a stream.
      virtual std::wostream &writeTextValue(std::wostream & wos, const Value& v, const Options* op = 0) const { std::ostringstream os; writeTextValue(os, v, op); wos << os; return (wos);}

        /// Reads a textual representation of the value's content from a stream.
        virtual std::wistream &readTextValue(std::wistream& , Value& v, const Options* = 0) const { throw StreamingNotSupportedException(StreamingNotSupportedException::TEXT_READ, v.getType().getExtendedTypeInfo()); }

        /// Writes a binary representation of the value's content to a stream.
        virtual std::ostream &writeBinaryValue(std::ostream &, const Value& v, const Options* = 0) const { throw StreamingNotSupportedException(StreamingNotSupportedException::BINARY_WRITE, v.getType().getExtendedTypeInfo()); }

        /// Reads a binary representation of the value's content from a stream.
        virtual std::istream &readBinaryValue(std::istream &, Value& v, const Options* = 0) const { throw StreamingNotSupportedException(StreamingNotSupportedException::BINARY_READ, v.getType().getExtendedTypeInfo()); }

        /// Virtual destructor.
        virtual ~ReaderWriter() {}
    };

    /// This class template provides basic default streaming capabilities
    /// for all types that define streaming operators (<< and >>). Most of
    /// the standard types are able to be read and written this way, so the
    /// StdReaderWriter template can be a convenient default for several
    /// types. The binary representation is a raw copy of the memory content.
    ///
    /// TO-DO: improve binary streaming and avoid arch dependency.
    ///
    template<typename T>
    class StdReaderWriter: public ReaderWriter
    {
    public:
        virtual std::ostream &writeTextValue(std::ostream &os, const Value& v, const Options * = 0) const
        {
            return (os << variant_cast<T>(v));
        }

        virtual std::istream &readTextValue(std::istream &is, Value& v, const Options * = 0) const
        {
            if (v.isEmpty()) v = Value(T());
            return (is >> variant_cast<T &>(v));
        }

        virtual std::ostream &writeBinaryValue(std::ostream &os, const Value& v, const Options * = 0) const
        {
            return os.write(reinterpret_cast<const char *>(extract_raw_data<T>(v)), sizeof(T));
        }

        virtual std::istream &readBinaryValue(std::istream &is, Value& v, const Options * = 0) const
        {
            if (v.isEmpty()) v = Value(T());
            return is.read(reinterpret_cast<char *>(extract_raw_data<T>(v)), sizeof(T));
        }

    };

    template<typename T>
    class StdWReaderWriter: public ReaderWriter
    {
    public:
        virtual std::wostream &writeTextValue(std::wostream &wos, const Value& v, const Options * = 0) const
        {
            return (wos << variant_cast<T>(v));
        }

        virtual std::wistream &readTextValue(std::wistream &wis, Value& v, const Options * = 0) const
        {
            if (v.isEmpty()) v = Value(T());
            return (wis >> variant_cast<T &>(v));
        }

        virtual std::ostream &writeBinaryValue(std::ostream &os, const Value& v, const Options * = 0) const
        {
            return os.write(reinterpret_cast<const char *>(extract_raw_data<T>(v)), sizeof(T));
        }

        virtual std::istream &readBinaryValue(std::istream &is, Value& v, const Options * = 0) const
        {
            if (v.isEmpty()) v = Value(T());
            return is.read(reinterpret_cast<char *>(extract_raw_data<T>(v)), sizeof(T));
        }

    };

    /// This ReaderWriter can be used to read and write enumeration values.
    /// The textual representation will be the enum label, if found, or the
    /// numerical value. The binary representation doesn't take label names
    /// into account.
    template<typename T>
    class EnumReaderWriter: public ReaderWriter
    {
        virtual std::ostream &writeTextValue(std::ostream &os, const Value& v, const Options *options = 0) const
        {
            int numeric = static_cast<int>(variant_cast<T>(v));

            if (!options || !options->getForceNumericOutput())
            {
                const Type& type = v.getType();
                const EnumLabelMap& elm = type.getEnumLabels();
                EnumLabelMap::const_iterator i = elm.find(numeric);
                if (i != elm.end())
                {
                    os << i->second;
                    return os;
                }
                else
                {
                    std::vector<std::string> labels;

                    // it could be a bitmask
                    for (EnumLabelMap::const_iterator i=elm.begin(); i!=elm.end(); ++i)
                    {
                        if (i->first != 0 && ((i->first & numeric) == i->first))
                        {
                            numeric ^= i->first;
                            labels.push_back(i->second);
                        }
                    }

                    // check whether all bits were discovered
                    if (numeric == 0)
                    {
                        for (std::vector<std::string>::const_iterator i=labels.begin(); i!=labels.end(); ++i)
                        {
                            os << *i;
                            if ((i+1) != labels.end()) os << " | ";
                        }
                        return os;
                    }
                }
            }

            return os << numeric;
        }

        virtual std::istream &readTextValue(std::istream &is, Value& v, const Options * = 0) const
        {
            if (v.isEmpty()) v = Value(T());

            int i;
            if (is >> i)
            {
                variant_cast<T &>(v) = static_cast<T>(i);
                return is;
            }

            is.clear();

            std::string s;            
            if (is >> s)
            {
                const Type& type = v.getType();
                const EnumLabelMap& elm = type.getEnumLabels();
                for (EnumLabelMap::const_iterator i=elm.begin(); i!=elm.end(); ++i)
                {
                    if (i->second.compare(s) == 0)
                    {
                        variant_cast<T &>(v) = static_cast<T>(i->first);
                        return is;
                    }
                }
            }

            return is;
        }

        virtual std::ostream &writeBinaryValue(std::ostream &os, const Value& v, const Options * = 0) const
        {
            return os.write(reinterpret_cast<const char *>(extract_raw_data<T>(v)), sizeof(T));
        }

        virtual std::istream &readBinaryValue(std::istream &is, Value& v, const Options * = 0) const
        {
            if (v.isEmpty())
                v = Value(T());
            return is.read(reinterpret_cast<char *>(extract_raw_data<T>(v)), sizeof(T));
        }

    };

    /// This is a ReaderWriter class that can be used to read and write
    /// pointer values. Note: template parameter T must be a pointer!
    template<typename T>
    class PtrReaderWriter: public ReaderWriter
    {
    public:
        virtual std::ostream &writeTextValue(std::ostream &os, const Value& v, const Options* = 0) const
        {
            return (os << (void*)variant_cast<T>(v));
        }

        virtual std::istream &readTextValue(std::istream &is, Value& v, const Options* = 0) const
        {
            void *ptr;
            is >> ptr;
            v = Value(T(ptr));
            return is;
        }

        virtual std::ostream &writeBinaryValue(std::ostream &os, const Value& v, const Options* = 0) const
        {
            return os.write(reinterpret_cast<const char *>(extract_raw_data<T>(v)), sizeof(T));
        }

        virtual std::istream &readBinaryValue(std::istream &is, Value& v, const Options* = 0) const
        {
            T ptr;
            is.read(reinterpret_cast<char *>(&ptr), sizeof(T));
            v = Value(ptr);
            return is;
        }
    };

}

#endif
