/*  -*-c++-*-
 *  Copyright (C) 2010 Cedric Pinson <cedric.pinson@plopbyte.net>
 *
 * 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.
 *
 * Authors:
 *         Cedric Pinson <cedric.pinson@plopbyte.net>
 */

#ifndef JSON_STREAM
#define JSON_STREAM

#include <iostream>
#include <string>
#include <cmath>
#include <limits>

#include <osgDB/fstream>

#include "utf8_string"


// A simple class wrapping ofstream calls to enable generic cleaning of json data.
// Especially 'standard' json should:
// * have utf-8 encoded string
// * disallow some control characters
// * does not support inf or nan values

#if defined(WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<=1700)
namespace std
{
    inline int isfinite( double x ) { return _finite( x ); }
    inline int isinf( double x ) { return !_finite( x ) && !_isnan( x ); }
}
#endif


class json_stream : public osgDB::ofstream {
    public:
        json_stream(const std::string& filename, bool strict=true) :
            _stream(filename.c_str()),
            _strict(strict)
        {}

        ~json_stream() {
            _stream.close();
        }

        operator bool() const {
            return _stream.is_open();
        }

        // forward std::endl
        typedef std::ostream& (*ostream_manipulator)(std::ostream&);
        json_stream& operator<<(ostream_manipulator pf) {
            if (_stream.is_open()) {
                _stream << pf;
            }
            return *this;
        }

        template<typename T>
        json_stream& operator<<(const T& data) {
            if (_stream.is_open()) {
                _stream << sanitize(data);
            }
            return *this;
        }

        template<typename T>
        const T sanitize(const T& t) {
            return t;
        }

        double sanitize(const float f) {
            return sanitize(static_cast<double>(f));
        }

        double sanitize(const double d) {
            if(_strict) {
                return to_valid_float(d);
            }
            return d;
        }

        double to_valid_float(const double d) {
            if(std::isfinite(d)) {
                return d;
            }
            else {
                if(std::isinf(d)) {
                    return std::numeric_limits<double>::max();
                }
                // no much way to do better than replace invalid float NaN by 0
                return 0.;
            }
        }

        std::string sanitize(const std::string& s) {
            if(_strict) {
                return to_json_utf8(s);
            }
            return s;
        }

        std::string sanitize(const char* s) {
            return sanitize(std::string(s));
        }

        std::string to_json_utf8(const std::string& s) {
            // TODO: try to decode latin1 if string is not valid utf8
            // before actually fixing bad 'chars'
            return utf8_string::sanitize(s);
        }


    protected:
        std::ofstream _stream;
        bool _strict;
};


#endif
