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

//
// Copyright(c) 2008 Skew Matrix Software LLC.
//

#include "VertexPaletteManager.h"
#include "DataOutputStream.h"
#include "Opcodes.h"
#include "Utils.h"
#include <osg/Notify>
#include <osg/Geometry>
#include <map>


namespace flt
{


VertexPaletteManager::VertexPaletteManager( const ExportOptions& fltOpt )
  : _currentSizeBytes( 8 ),
    _current( NULL ),
    _vertices( NULL ),
    _fltOpt( fltOpt )
{
}

VertexPaletteManager::~VertexPaletteManager()
{
    if (!_verticesTempName.empty())
    {
        // Delete our temp file.
        if (_verticesStr.is_open())
        {
            osg::notify( osg::WARN ) << "fltexp: VertexPaletteManager destructor has an open temp file." << std::endl;
            // This should not happen. FltExportVisitor::complete should close
            // this file before we get to this destructor.
            return;
        }
        osg::notify( osg::INFO ) << "fltexp: Deleting temp file " << _verticesTempName << std::endl;    
        FLTEXP_DELETEFILE( _verticesTempName.c_str() );
    }
}

void
VertexPaletteManager::add( const osg::Geometry& geom )
{
    const osg::Array* v = geom.getVertexArray();
    if (!v)
    {
        osg::notify( osg::WARN ) << "fltexp: Attempting to add NULL vertex array in VertexPaletteManager." << std::endl;
        return;
    }
    const osg::Array* c = geom.getColorArray();
    const osg::Array* n = geom.getNormalArray();
    const osg::Array* t = geom.getTexCoordArray( 0 );
    
    const unsigned int size = v->getNumElements();
    osg::ref_ptr< const osg::Vec3dArray > v3 = asVec3dArray( v, size );
    osg::ref_ptr< const osg::Vec4Array > c4 = asVec4Array( c, size );
    osg::ref_ptr< const osg::Vec3Array > n3 = asVec3Array( n, size );
    osg::ref_ptr< const osg::Vec2Array > t2 = asVec2Array( t, size );
    if (v && !v3)
        return;
    if (c && !c4)
        return;
    if (n && !n3)
        return;
    if (t && !t2)
        return;

    const bool cpv =( geom.getColorBinding() == osg::Geometry::BIND_PER_VERTEX );
    const bool npv =( geom.getNormalBinding() == osg::Geometry::BIND_PER_VERTEX );
    add( v, v3.get(), c4.get(), n3.get(), t2.get(), cpv, npv );
}
void
VertexPaletteManager::add( const osg::Array* key,
    const osg::Vec3dArray* v, const osg::Vec4Array* c,
    const osg::Vec3Array* n, const osg::Vec2Array* t,
    bool colorPerVertex, bool normalPerVertex, bool allowSharing )
{
    bool needsInit( true );

    if (allowSharing)
    {
        ArrayMap::iterator it = _arrayMap.find( key );
        if (it != _arrayMap.end())
            needsInit = false;
        _current = &( _arrayMap[ key ] );
    }
    else
    {
        _current = &( _nonShared );
    }

    if (needsInit)
    {
        _current->_byteStart = _currentSizeBytes;

        _current->_idxCount = v->size();

        _current->_idxSizeBytes = recordSize( recordType( v, c, n, t ) );
        _currentSizeBytes += ( _current->_idxSizeBytes * _current->_idxCount );

        // Next we'll write the vertex palette record data. But,
        //   if we don't have a DataOutputStream yet, open the temp file.
        if (!_vertices)
        {
            _verticesTempName = _fltOpt.getTempDir() + "/ofw_temp_vertices";
            _verticesStr.open( _verticesTempName.c_str(), std::ios::out | std::ios::binary );
            _vertices = new DataOutputStream( _verticesStr.rdbuf(), _fltOpt.getValidateOnly() );
        }
        writeRecords( v, c, n, t, colorPerVertex, normalPerVertex );
    }
}

unsigned int
VertexPaletteManager::byteOffset( unsigned int idx ) const
{
    if (!_current)
    {
        osg::notify( osg::WARN ) << "fltexp: No current vertex array in VertexPaletteManager." << std::endl;
        return 4;
    }
    if (idx >= _current->_idxCount)
    {
        osg::notify( osg::WARN ) << "fltexp: Index out of range in VertexPaletteManager." << std::endl;
        return 4;
    }

    return( _current->_byteStart + (_current->_idxSizeBytes * idx) );
}

void
VertexPaletteManager::write( DataOutputStream& dos ) const
{
    if (_currentSizeBytes == 8)
        // Empty palette. Don't write anything.
        return;

    dos.writeInt16( (int16) VERTEX_PALETTE_OP );
    dos.writeUInt16( 8 );
    dos.writeInt32( _currentSizeBytes );

    // Close the temp file. We're done writing new data to it.
    _verticesStr.close();

    // Open that temp file again, this time for reading.
    //   Then copy to dos.
    char buf;
    osgDB::ifstream vertIn;
    vertIn.open( _verticesTempName.c_str(), std::ios::in | std::ios::binary );
    while (!vertIn.eof() )
    {
        vertIn.read( &buf, 1 );
        if (vertIn.good())
            dos << buf;
    }
    vertIn.close();
}



VertexPaletteManager::PaletteRecordType
VertexPaletteManager::recordType( const osg::Array* v, const osg::Array* c,
    const osg::Array* n, const osg::Array* t )
{
    if (t)
    {
        // Texture coordinates
        if (n)
            return VERTEX_CNT;
        else
            return VERTEX_CT;
    }
    else
    {
        // No texture coordinates
        if (n)
            return VERTEX_CN;
        else
            return VERTEX_C;
    }
}

unsigned int
VertexPaletteManager::recordSize( PaletteRecordType recType )
{
    switch (recType)
    {
    case VERTEX_C:
        return 40;
        break;
    case VERTEX_CN:
        return (_fltOpt.getFlightFileVersionNumber() > ExportOptions::VERSION_15_7) ? 56 : 52;
        break;
    case VERTEX_CT:
        return 48;
        break;
    case VERTEX_CNT:
        return 64;
        break;
    default:
        return 0;
    }
}

void
VertexPaletteManager::writeRecords( const osg::Vec3dArray* v, const osg::Vec4Array* c,
    const osg::Vec3Array* n, const osg::Vec2Array* t,
    bool colorPerVertex, bool normalPerVertex )
{
    const PaletteRecordType recType = recordType( v, c, n, t );
    const int16 sizeBytes = recordSize( recType );

    int16 opcode = 0;
    switch( recType )
    {
    case VERTEX_C:
        opcode = VERTEX_C_OP;
        break;
    case VERTEX_CN:
        opcode = VERTEX_CN_OP;
        if (!n)
            osg::notify( osg::WARN ) << "fltexp: VPM::writeRecords: no normal array." << std::endl;
        break;
    case VERTEX_CNT:
        opcode = VERTEX_CNT_OP;
        if (!n)
            osg::notify( osg::WARN ) << "fltexp: VPM::writeRecords: no normal array." << std::endl;
        if (!t)
            osg::notify( osg::WARN ) << "fltexp: VPM::writeRecords: no tex coord array." << std::endl;
        break;
    case VERTEX_CT:
        opcode = VERTEX_CT_OP;
        if (!t)
            osg::notify( osg::WARN ) << "fltexp: VPM::writeRecords: no tex coord array." << std::endl;
        break;
    }

    enum FlagBits
    {
        START_HARD_EDGE = (0x8000 >> 0),
        NORMAL_FROZEN   = (0x8000 >> 1),
        NO_COLOR        = (0x8000 >> 2),
        PACKED_COLOR    = (0x8000 >> 3)
    };
    uint32 flags( NO_COLOR );
    if (colorPerVertex)
        flags = PACKED_COLOR;


    size_t idx;
    for( idx=0; idx<v->size(); idx++)
    {
        uint32  packedColor( 0 );
        if (c && colorPerVertex)
        {
            osg::Vec4 color = (*c)[ idx ];
            packedColor = (int)(color[3]*255) << 24 |
                (int)(color[2]*255) << 16 | (int)(color[1]*255) << 8 |
                (int)(color[0]*255);
        }

        // Write fields common to all record types.
        _vertices->writeInt16( opcode );
        _vertices->writeUInt16( sizeBytes );
        _vertices->writeUInt16( 0 ); // Color name
        _vertices->writeInt16( flags ); // Flags
        _vertices->writeVec3d( (*v)[ idx ] ); // Vertex

        // Now write record-specific fields.
        switch( recType )
        {
        case VERTEX_C:
        {
            _vertices->writeInt32( packedColor ); // Packed color
            _vertices->writeUInt32( 0 ); // Vertex color index
            break;
        }
        case VERTEX_CN:
        {
            if (!normalPerVertex) // Normal
                _vertices->writeVec3f( (*n)[ 0 ] );
            else
                _vertices->writeVec3f( (*n)[ idx ] );
            _vertices->writeInt32( packedColor ); // Packed color
            _vertices->writeUInt32( 0 ); // Vertex color index
            if (_fltOpt.getFlightFileVersionNumber() > ExportOptions::VERSION_15_7)
                _vertices->writeUInt32( 0 ); // Reserved
            break;
        }
        case VERTEX_CNT:
        {
            if (!normalPerVertex) // Normal
                _vertices->writeVec3f( (*n)[ 0 ] );
            else
                _vertices->writeVec3f( (*n)[ idx ] );
            _vertices->writeVec2f( (*t)[ idx ] ); // Tex coord
            _vertices->writeInt32( packedColor ); // Packed color
            _vertices->writeUInt32( 0 ); // Vertex color index
            _vertices->writeUInt32( 0 ); // Reserved
            break;
        }
        case VERTEX_CT:
        {
            _vertices->writeVec2f( (*t)[ idx ] ); // Tex coord
            _vertices->writeInt32( packedColor ); // Packed color
            _vertices->writeUInt32( 0 ); // Vertex color index
            break;
        }
        }
    }
}



osg::ref_ptr< const osg::Vec2Array >
VertexPaletteManager::asVec2Array( const osg::Array* in, const unsigned int n )
{
    if (!in)
        return NULL;

    osg::Array::Type arrayType = in->getType();
    if (arrayType == osg::Array::Vec2ArrayType)
    {
        if (n <= in->getNumElements())
        {
            osg::ref_ptr< const osg::Vec2Array > v2f =
                dynamic_cast< const osg::Vec2Array* >( in );
            return v2f;
        }
    }

    const unsigned int nToCopy = ( (n < in->getNumElements()) ? n : in->getNumElements() );
    osg::ref_ptr< osg::Vec2Array > ret = new osg::Vec2Array( n );

    switch( arrayType )
    {
    case osg::Array::Vec2ArrayType:
    {
        // No need to convert data, but must copy into correctly-sized array.
        // If the size was correct, we wouldn't be here.
        osg::ref_ptr< const osg::Vec2Array > v2f =
            dynamic_cast< const osg::Vec2Array* >( in );
        ret->assign( v2f->begin(), v2f->end() );;
        ret->resize( n );
        return ret.get();
    }
    case osg::Array::Vec2dArrayType:
    {
        osg::ref_ptr< const osg::Vec2dArray > v2d =
            dynamic_cast< const osg::Vec2dArray* >( in );
        unsigned int idx;
        for (idx=0; idx<nToCopy; idx++ )
            (*ret)[ idx ] = (*v2d)[ idx ]; // convert Vec2 double to Vec2 float
        return ret.get();
    }
    default:
    {
        osg::notify( osg::WARN ) << "fltexp: Unsupported array type in conversion to Vec2Array: " << arrayType << std::endl;
        return NULL;
    }
    }
}
osg::ref_ptr< const osg::Vec3Array >
VertexPaletteManager::asVec3Array( const osg::Array* in, const unsigned int n )
{
    if (!in)
        return NULL;

    osg::Array::Type arrayType = in->getType();
    if (arrayType == osg::Array::Vec3ArrayType)
    {
        if (n <= in->getNumElements())
        {
            osg::ref_ptr< const osg::Vec3Array > v3f =
                dynamic_cast< const osg::Vec3Array* >( in );
            return v3f;
        }
    }

    const unsigned int nToCopy = ( (n < in->getNumElements()) ? n : in->getNumElements() );
    osg::ref_ptr< osg::Vec3Array > ret = new osg::Vec3Array( n );

    switch( arrayType )
    {
    case osg::Array::Vec3ArrayType:
    {
        // No need to convert data, but must copy into correctly-sized array.
        // If the size was correct, we wouldn't be here.
        osg::ref_ptr< const osg::Vec3Array > v3f =
            dynamic_cast< const osg::Vec3Array* >( in );
        ret->assign( v3f->begin(), v3f->end() );;
        ret->resize( n );
        return ret.get();
    }
    case osg::Array::Vec3dArrayType:
    {
        osg::ref_ptr< const osg::Vec3dArray > v3d =
            dynamic_cast< const osg::Vec3dArray* >( in );
        unsigned int idx;
        for (idx=0; idx<nToCopy; idx++ )
            (*ret)[ idx ] = (*v3d)[ idx ]; // convert Vec3 double to Vec3 float
        return ret.get();
    }
    default:
    {
        osg::notify( osg::WARN ) << "fltexp: Unsupported array type in conversion to Vec3Array: " << arrayType << std::endl;
        return NULL;
    }
    }
}
osg::ref_ptr< const osg::Vec3dArray >
VertexPaletteManager::asVec3dArray( const osg::Array* in, const unsigned int n )
{
    if (!in)
        return NULL;

    osg::Array::Type arrayType = in->getType();
    if (arrayType == osg::Array::Vec3dArrayType)
    {
        if (n <= in->getNumElements())
        {
            osg::ref_ptr< const osg::Vec3dArray > v3d =
                dynamic_cast< const osg::Vec3dArray* >( in );
            return v3d;
        }
    }

    const unsigned int nToCopy = ( (n < in->getNumElements()) ? n : in->getNumElements() );
    osg::ref_ptr< osg::Vec3dArray > ret = new osg::Vec3dArray( n );

    switch( arrayType )
    {
    case osg::Array::Vec3dArrayType:
    {
        // No need to convert data, but must copy into correctly-sized array.
        // If the size was correct, we wouldn't be here.
        osg::ref_ptr< const osg::Vec3dArray > v3d =
            dynamic_cast< const osg::Vec3dArray* >( in );
        ret->assign( v3d->begin(), v3d->end() );;
        ret->resize( n );
        return ret.get();
    }
    case osg::Array::Vec3ArrayType:
    {
        osg::ref_ptr< const osg::Vec3Array > v3f =
            dynamic_cast< const osg::Vec3Array* >( in );
        unsigned int idx;
        for (idx=0; idx<nToCopy; idx++ )
            (*ret)[ idx ] = (*v3f)[ idx ]; // convert Vec3 float to Vec3 double
        return ret.get();
    }
    default:
    {
        osg::notify( osg::WARN ) << "fltexp: Unsupported array type in conversion to Vec3dArray: " << arrayType << std::endl;
        return NULL;
    }
    }
}
osg::ref_ptr< const osg::Vec4Array >
VertexPaletteManager::asVec4Array( const osg::Array* in, const unsigned int n )
{
    if (!in)
        return NULL;

    osg::Array::Type arrayType = in->getType();
    if (arrayType == osg::Array::Vec4ArrayType)
    {
        if (n <= in->getNumElements())
        {
            osg::ref_ptr< const osg::Vec4Array > v4f =
                dynamic_cast< const osg::Vec4Array* >( in );
            return v4f;
        }
    }

    const unsigned int nToCopy = ( (n < in->getNumElements()) ? n : in->getNumElements() );
    osg::ref_ptr< osg::Vec4Array > ret = new osg::Vec4Array( n );

    switch( arrayType )
    {
    case osg::Array::Vec4ArrayType:
    {
        // No need to convert data, but must copy into correctly-sized array.
        // If the size was correct, we wouldn't be here.
        osg::ref_ptr< const osg::Vec4Array > v4f =
            dynamic_cast< const osg::Vec4Array* >( in );
        ret->assign( v4f->begin(), v4f->end() );;
        ret->resize( n );
        return ret.get();
    }
    case osg::Array::Vec4ubArrayType:
    {
        osg::ref_ptr< const osg::Vec4ubArray > v4ub =
            dynamic_cast< const osg::Vec4ubArray* >( in );
        unsigned int idx;
        for (idx=0; idx<nToCopy; idx++ )
        {
            // convert Vec4 unsigned byte to Vec4 float
            osg::Vec4& dest = (*ret)[ idx ];
            osg::Vec4ub src = (*v4ub)[ idx ];
            dest[0] = src[0] / 255.f;
            dest[1] = src[1] / 255.f;
            dest[2] = src[2] / 255.f;
            dest[3] = src[3] / 255.f;
        }
        return ret.get();
    }
    default:
    {
        osg::notify( osg::WARN ) << "fltexp: Unsupported array type in conversion to Vec4Array: " << arrayType << std::endl;
        return NULL;
    }
    }
}



VertexPaletteManager::ArrayInfo::ArrayInfo()
  : _byteStart( 0 ),
    _idxSizeBytes( 0 ),
    _idxCount( 0 )
{
}


}
