/* -*-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.
*/

//
// OpenFlightŪ loader for OpenSceneGraph
//
//  Copyright (C) 2005-2007  Brede Johansen
//

#include <osgSim/MultiSwitch>
#include <osgSim/LightPointSystem>
#include <osgSim/LightPointNode>
#include <osg/Texture2D>
#include "Registry.h"
#include "Document.h"
#include "RecordInputStream.h"

using namespace flt;

/** LightPoint
*/
class LightPoint : public PrimaryRecord
{
    enum Directionality
    {
        OMNIDIRECTIONAL = 0,
        UNIDIRECTIONAL = 1,
        BIDIRECTIONAL = 2
    };

    // flags
    enum Flags
    {
                                                // bit 0 = reserved
        NO_BACK_COLOR    = 0x80000000u >> 1,        // bit 1 = no back color
                                                // bit 2 = reserved
        CALLIGRAPHIC    = 0x80000000u >> 3,        // bit 3 = calligraphic proximity occulting
        REFLECTIVE        = 0x80000000u >> 4,        // bit 4 = reflective, non-emissive point
                                                // bit 5-7 = randomize intensity
                                                //   0 = never
                                                //     1 = low
                                                //   2 = medium
                                                //   3 = high
        PERSPECTIVE        = 0x80000000u >> 8,        // bit 8 = perspective mode
        FLASHING        = 0x80000000u >> 9,        // bit 9 = flashing
        ROTATING        = 0x80000000u >> 10,    // bit 10 = rotating
        ROTATE_CC        = 0x80000000u >> 11,    // bit 11 = rotate counter clockwise
                                                // bit 12 = reserved
                                                // bit 13-14 = quality
                                                //   0 = low
                                                //     1 = medium
                                                //   2 = high
                                                //   3 = undefined
        VISIBLE_DAY        = 0x80000000u >> 15,    // bit 15 = visible during day
        VISIBLE_DUSK    = 0x80000000u >> 16,    // bit 16 = visible during dusk
        VISIBLE_NIGHT    = 0x80000000u >> 17        // bit 17 = visible during night
                                                // bit 18-31 = spare
    };


    int16   _material;
    int16   _feature;
    osg::Vec4f _backColor;
    int32   _displayMode;
    float32 _intensityFront;
    float32 _intensityBack;
    float32 _minDefocus;
    float32 _maxDefocus;
    int32   _fadeMode;
    int32   _fogPunchMode;
    int32   _directionalMode;
    int32   _rangeMode;
    float32 _minPixelSize;
    float32 _maxPixelSize;
    float32 _actualPixelSize;
    float32 _transparentFalloff;
    float32 _transparentFalloffExponent;
    float32 _transparentFalloffScalar;
    float32 _transparentFalloffClamp;
    float32 _fog;
    float32 _sizeDifferenceThreshold;
    int32   _directionality;
    float32 _lobeHorizontal;
    float32 _lobeVertical;
    float32 _lobeRoll;
    float32 _falloff;
    float32 _ambientIntensity;
    float32 _animationPeriod;
    float32 _animationPhaseDelay;
    float32 _animationPeriodEnable;
    float32 _significance;
    int32   _drawOrder;
    uint32  _flags;
    osg::Vec3f _animationAxis;

    osg::ref_ptr<osgSim::LightPointNode> _lpn;

public:

    LightPoint() {}

    META_Record(LightPoint)

    META_setID(_lpn)
    META_setComment(_lpn)
    META_dispose(_lpn)

    // Add lightpoint, add two if bidirectional.
    virtual void addVertex(Vertex& vertex)
    {
        osgSim::LightPoint lp;
        lp._position = vertex._coord;
        lp._radius = 0.5f * _actualPixelSize;
        lp._intensity = _intensityFront;

        // color
        lp._color = (vertex.validColor()) ? vertex._color : osg::Vec4(1,1,1,1);

        // sector
        bool directional = (_directionality==UNIDIRECTIONAL) || (_directionality==BIDIRECTIONAL);
        if (directional && vertex.validNormal())
        {
            lp._sector = new osgSim::DirectionalSector(
                vertex._normal,
                osg::DegreesToRadians(_lobeHorizontal),
                osg::DegreesToRadians(_lobeVertical),
                osg::DegreesToRadians(_lobeRoll));
        }

        // if the flashing or rotating bit is set in the flags, add a blink sequence
        if ((_flags & FLASHING) || (_flags & ROTATING))
        {
            lp._blinkSequence = new osgSim::BlinkSequence();
            if (lp._blinkSequence.valid())
            {
                lp._blinkSequence->setDataVariance(osg::Object::DYNAMIC);
                lp._blinkSequence->setPhaseShift(_animationPhaseDelay);
                lp._blinkSequence->addPulse(_animationPeriod - _animationPeriodEnable,
                    osg::Vec4f(0.0f, 0.0f, 0.0f, 0.0f));
                lp._blinkSequence->addPulse(_animationPeriodEnable, lp._color);
            }
        }

        _lpn->addLightPoint(lp);

        // Create a new lightpoint if bi-directional.
        if ((_directionality==BIDIRECTIONAL) && vertex.validNormal())
        {
            // back intensity
            lp._intensity = _intensityBack;

            // back color
            if (!(_flags & NO_BACK_COLOR))
                lp._color = _backColor;

            // back sector
            lp._sector = new osgSim::DirectionalSector(
                -vertex._normal,
                osg::DegreesToRadians(_lobeHorizontal),
                osg::DegreesToRadians(_lobeVertical),
                osg::DegreesToRadians(_lobeRoll));

            _lpn->addLightPoint(lp);
        }
    }

protected:

    virtual ~LightPoint() {}

    virtual void readRecord(RecordInputStream& in, Document& document)
    {
        std::string id = in.readString(8);
        _material = in.readInt16();
        _feature = in.readInt16();

        int32 backColorIndex = in.readInt32();

        _backColor = document.getColorPool() ?
                            document.getColorPool()->getColor(backColorIndex) :
                            osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f);

        _displayMode = in.readInt32();
        _intensityFront = in.readFloat32();
        _intensityBack = in.readFloat32();
        _minDefocus = in.readFloat32();
        _maxDefocus = in.readFloat32();
        _fadeMode = in.readInt32();
        _fogPunchMode = in.readInt32();
        _directionalMode = in.readInt32();
        _rangeMode = in.readInt32();
        _minPixelSize = in.readFloat32(); // * document.unitScale();
        _maxPixelSize = in.readFloat32(); // * document.unitScale();
        _actualPixelSize = in.readFloat32(); // * document.unitScale();
        _transparentFalloff = in.readFloat32();
        _transparentFalloffExponent = in.readFloat32();
        _transparentFalloffScalar = in.readFloat32();
        _transparentFalloffClamp = in.readFloat32();
        _fog = in.readFloat32();
        in.forward(4);
        _sizeDifferenceThreshold = in.readFloat32();
        _directionality = in.readInt32();
        _lobeHorizontal = in.readFloat32();
        _lobeVertical = in.readFloat32();
        _lobeRoll = in.readFloat32();
        _falloff = in.readFloat32();
        _ambientIntensity = in.readFloat32();
        _animationPeriod = in.readFloat32();
        _animationPhaseDelay = in.readFloat32();
        _animationPeriodEnable = in.readFloat32();
        _significance = in.readFloat32();
        _drawOrder = in.readInt32();
        _flags = in.readUInt32(0);
        _animationAxis = in.readVec3f();

        _lpn = new osgSim::LightPointNode;
        _lpn->setName(id);
        _lpn->setMinPixelSize(_minPixelSize);
        _lpn->setMaxPixelSize(_maxPixelSize);

        // Add to parent
        if (_parent.valid())
            _parent->addChild(*_lpn);
    }
};

REGISTER_FLTRECORD(LightPoint, LIGHT_POINT_OP)



/** IndexedLightPoint
*/
class IndexedLightPoint : public PrimaryRecord
{
    enum Directionality
    {
        OMNIDIRECTIONAL = 0,
        UNIDIRECTIONAL = 1,
        BIDIRECTIONAL = 2
    };

    // flags
    static const unsigned int NO_BACK_COLOR_BIT = 0x80000000u >> 1;

    osg::ref_ptr<osgSim::LightPointNode> _lpn;
    osg::ref_ptr<LPAppearance> _appearance;
    osg::ref_ptr<LPAnimation> _animation;

public:

    IndexedLightPoint() {}

    META_Record(IndexedLightPoint)

    META_setID(_lpn)
    META_setComment(_lpn)
    META_dispose(_lpn)

    // Add lightpoint, add two if bidirectional.
    virtual void addVertex(Vertex& vertex)
    {
        osgSim::LightPoint lp;

        if (_appearance.valid())
        {
            lp._position = vertex._coord;
            lp._radius = 0.5f * _appearance->actualPixelSize;
            lp._intensity = _appearance->intensityFront;

            // color
            lp._color = (vertex.validColor()) ? vertex._color : osg::Vec4(1,1,1,1);

            // sector
            bool directional = (_appearance->directionality==UNIDIRECTIONAL) || (_appearance->directionality==BIDIRECTIONAL);
            if (directional && vertex.validNormal())
            {
                lp._sector = new osgSim::DirectionalSector(
                    vertex._normal,
                    osg::DegreesToRadians(_appearance->horizontalLobeAngle),
                    osg::DegreesToRadians(_appearance->verticalLobeAngle),
                    osg::DegreesToRadians(_appearance->lobeRollAngle));
            }

            // Blink sequence
            if (_animation.valid())
            {
                osgSim::BlinkSequence* blinkSequence = new osgSim::BlinkSequence;
                blinkSequence->setName(_animation->name);

                switch (_animation->animationType)
                {
                case LPAnimation::ROTATING:
                case LPAnimation::STROBE:
                    blinkSequence->setPhaseShift(_animation->animationPhaseDelay);
                    blinkSequence->addPulse(_animation->animationPeriod-_animation->animationEnabledPeriod, osg::Vec4(0,0,0,0));
                    blinkSequence->addPulse(_animation->animationEnabledPeriod, lp._color);
                    break;

                case LPAnimation::MORSE_CODE:
                    // todo
                    //blinkSequence->addPulse(double length,lp._color);
                    break;

                case LPAnimation::FLASHING_SEQUENCE:
                    {
                        blinkSequence->setPhaseShift(_animation->animationPhaseDelay);

                        for (LPAnimation::PulseArray::iterator itr=_animation->sequence.begin();
                            itr!=_animation->sequence.end();
                            ++itr)
                        {
                            double duration = itr->duration;

                            osg::Vec4 color;
                            switch (itr->state)
                            {
                            case LPAnimation::ON:
                                color = lp._color;
                                break;
                            case LPAnimation::OFF:
                                color = osg::Vec4(0,0,0,0);
                                break;
                            case LPAnimation::COLOR_CHANGE:
                                color = itr->color;
                                break;
                            }

                            blinkSequence->addPulse(duration, color);
                        }
                    }
                    break;
                }

                lp._blinkSequence = blinkSequence;
            }

            _lpn->addLightPoint(lp);

            // Create a new lightpoint if bi-directional.
            if ((_appearance->directionality==BIDIRECTIONAL) && vertex.validNormal())
            {
                // back intensity
                lp._intensity = _appearance->intensityBack;

                // back color
                if (!(_appearance->flags & NO_BACK_COLOR_BIT))
                    lp._color = _appearance->backColor;

                // back sector
                lp._sector = new osgSim::DirectionalSector(
                    -vertex._normal,
                    osg::DegreesToRadians(_appearance->horizontalLobeAngle),
                    osg::DegreesToRadians(_appearance->verticalLobeAngle),
                    osg::DegreesToRadians(_appearance->lobeRollAngle));

                _lpn->addLightPoint(lp);
            }
        }
    }


protected:

    virtual ~IndexedLightPoint() {}

    virtual void readRecord(RecordInputStream& in, Document& document)
    {
        std::string id = in.readString(8);
        int32 appearanceIndex = in.readInt32();
        int32 animationIndex = in.readInt32();
        /*int32 drawOrder =*/ in.readInt32();           // for calligraphic lights

        LightPointAppearancePool* lpAppearancePool = document.getOrCreateLightPointAppearancePool();
        _appearance = lpAppearancePool->get(appearanceIndex);

        LightPointAnimationPool* lpAnimationPool = document.getOrCreateLightPointAnimationPool();
        _animation = lpAnimationPool->get(animationIndex);

        _lpn = new osgSim::LightPointNode;
        _lpn->setName(id);

        if (_appearance.valid())
        {
            _lpn->setMinPixelSize(_appearance->minPixelSize);
            _lpn->setMaxPixelSize(_appearance->maxPixelSize);

            if (_appearance->texturePatternIndex != -1)
            {
                // Use point sprites for light points.
                _lpn->setPointSprite();

                TexturePool* tp = document.getOrCreateTexturePool();
                osg::StateSet* textureStateSet = tp->get(_appearance->texturePatternIndex);
                if (textureStateSet)
                {
                    // Merge face stateset with texture stateset
                    osg::StateSet* stateset = _lpn->getOrCreateStateSet();
                    stateset->merge(*textureStateSet);
                }
            }
        }

        // Add to parent
        if (_parent.valid())
            _parent->addChild(*_lpn);

    }
};

REGISTER_FLTRECORD(IndexedLightPoint, INDEXED_LIGHT_POINT_OP)



/** LightPointSystem
*/
class LightPointSystem : public PrimaryRecord
{
    float32 _intensity;
    int32 _animationState;
    int32  _flags;

    osg::ref_ptr<osgSim::MultiSwitch> _switch;
    osg::ref_ptr<osgSim::LightPointSystem> _lps;

public:

    LightPointSystem():
        _intensity(1.0f),
        _animationState(0),
        _flags(0)
    {}

    META_Record(LightPointSystem)
    META_addChild(_switch)

protected:

    virtual ~LightPointSystem() {}

    virtual void readRecord(RecordInputStream& in, Document& document)
    {
        std::string id = in.readString(8);

        _intensity = in.readFloat32();
        _animationState = in.readInt32(0);
        _flags = in.readInt32(0);

        _switch = new osgSim::MultiSwitch;
        _lps = new osgSim::LightPointSystem;

        _switch->setName(id);
        _lps->setName(id);
        _lps->setIntensity(_intensity);

        switch (_animationState)
        {
            // Note that OpenFlight 15.8 spec says 0 means on and 1 means off.
            // However, if animation is set on in Creator, it stores a 1, and
            // a zero is stored for off! So, for now, we ignore the spec...
            case 0:
                _lps->setAnimationState( osgSim::LightPointSystem::ANIMATION_OFF );
                break;
            default:
            case 1:
                _lps->setAnimationState( osgSim::LightPointSystem::ANIMATION_ON );
                break;
            case 2:
                _lps->setAnimationState( osgSim::LightPointSystem::ANIMATION_RANDOM );
                break;
        }

        if (_parent.valid())
            _parent->addChild(*((osg::Group*)_switch.get()));
    }

    virtual void dispose(Document& document)
    {
        if (!_switch.valid()) return;

        // Insert transform(s)
        if (_matrix.valid())
        {
            insertMatrixTransform(*_switch,*_matrix,_numberOfReplications);
        }

        // Set default sets: 0 for all off, 1 for all on
        _switch->setAllChildrenOff( 0 );
        _switch->setAllChildrenOn( 1 );

        // set initial on/off state
        unsigned int initialSet = ( (_flags & 0x80000000) != 0 ) ? 1 : 0;
        _switch->setActiveSwitchSet( initialSet );

        for (unsigned int i = 0; i < _switch->getNumChildren(); i++)
        {
            osg::Node* child = _switch->getChild(i);
            if (osgSim::LightPointNode* lpn = dynamic_cast<osgSim::LightPointNode*>(child))
                lpn->setLightPointSystem(_lps.get());
        }
    }
};

REGISTER_FLTRECORD(LightPointSystem, LIGHT_POINT_SYSTEM_OP)


