#include "AnimationExtractor.h"

#include "../../common/Logger.h"



void
AnimationExtractor( const Ogre::SkeletonPtr& skeleton, const ModelInfo& info, Skeleton& skeleton_data )
{
    if( info.animations.size() != info.animations_name.size() )
    {
        LOGGER->Log( "AnimationExtractor ERROR: number of animations dont match number of animation names.\n" );
        return;
    }

    float fps = 25.0f;

    for( size_t i = 0; i < info.animations.size(); ++i )
    {
        if( info.animations[ i ] == "" || info.animations_name[ i ] == "" )
        {
            continue;
        }
        File* file = new File( "./data/field/" + info.animations[ i ] );

        LOGGER->Log( "AnimationExtractor: " + Ogre::StringConverter::toString( i ) + " " + info.animations[ i ] + " as animation with name " + info.animations_name[ i ] + "\n" );

        int number_of_frames = file->GetU16LE( 0x2 );
        Ogre::Animation* anim = skeleton->createAnimation( info.animations_name[ i ], ( number_of_frames - 1 ) / fps );

        float time = 0;
        float tx, ty, tz;
        u32 raw_rx, raw_ry, raw_rz;
        float rx, ry, rz;
        Ogre::Bone* bone1;
        Ogre::Bone* bone2;
        Ogre::NodeAnimationTrack* track1;
        Ogre::NodeAnimationTrack* track2;
        Ogre::TransformKeyFrame* frame1;
        Ogre::TransformKeyFrame* frame2;
        Ogre::Quaternion rot;
        Ogre::Matrix3 mat;

        for ( int frame = 0; frame < number_of_frames; ++frame )
        {
            // this is just to orientate model to right direction
            if( frame == 0 )
            {
                bone1 = skeleton->getBone( 0 );
                track1 = anim->createNodeTrack( 0, bone1 );
                track1->removeAllKeyFrames();
                frame1 = track1->createNodeKeyFrame( time );
                mat.FromEulerAnglesYXZ( Ogre::Radian( Ogre::Degree( 0 ) ), Ogre::Radian( Ogre::Degree( -90 ) ), Ogre::Radian( Ogre::Degree( 0 ) ) );
                rot.FromRotationMatrix( mat );
                frame1->setRotation( rot );
                bone2 = skeleton->getBone( 1 );
                track2 = anim->createNodeTrack( 1, bone2 );
                track2->removeAllKeyFrames();
            }
            else
            {
                bone1 = skeleton->getBone( 0 );
                track1 = anim->getNodeTrack( 0 );
                frame1 = track1->createNodeKeyFrame( time );
                mat.FromEulerAnglesYXZ( Ogre::Radian( Ogre::Degree( 0 ) ), Ogre::Radian(Ogre::Degree( -90 ) ), Ogre::Radian( Ogre::Degree( 0 ) ) );
                rot.FromRotationMatrix( mat );
                frame1->setRotation( rot );
                bone2 = skeleton->getBone( 1 );
                track2 = anim->getNodeTrack( 1 );
            }
            frame2 = track2->createNodeKeyFrame( time );



            u32 high_angle_offset = file->GetU32LE( 0xc );
            u32 low_angle_offset = file->GetU32LE( 0x10 );

            for( int bone = 0; bone < skeleton_data.size(); ++bone )
            {
                tx = 0; ty = 0; tz = 0;
                rx = 0; ry = 0; rz = 0;
                raw_rx = 0; raw_ry = 0; raw_rz = 0;

                if( bone == 0 )
                {
                    // create root translation
                    u16 flags = file->GetU16LE( 0xa );
                    if( flags & 1 )
                    {
                        tx = ( s16 )file->GetU16LE( 0x4 );
                    }
                    else
                    {
                        u16 offset = file->GetU16LE( 0x4 );
                        tx = ( s16 )file->GetU16LE( offset + frame * 2 );
                    }
                    if( flags & 2 )
                    {
                        ty = ( s16 )file->GetU16LE( 0x6 );
                    }
                    else
                    {
                        u16 offset = file->GetU16LE( 0x6 );
                        ty = ( s16 )file->GetU16LE( offset + frame * 2 );
                    }
                    if( flags & 4 )
                    {
                        tz = ( s16 )file->GetU16LE( 0x8 );
                    }
                    else
                    {
                        u16 offset = file->GetU16LE( 0x8 );
                        tz = ( s16 )file->GetU16LE( offset + frame * 2 );
                    }
                }



                u16 flags = file->GetU16LE( high_angle_offset + 0x6 );

                u16 data = file->GetU16LE( high_angle_offset + 0x0 );
                //LOGGER->Log( "AnimationExtractor: data_x = 0x" + ToHexString( data, 4, '0' ) + ".\n" );
                if( ( flags & 1 ) == 0 )
                {
                    data = file->GetU8( data + frame );
                    //LOGGER->Log( "AnimationExtractor: flagged data_x = 0x" + ToHexString( data, 4, '0' ) + ".\n" );
                }
                raw_rx = ( data & 0xff ) << 4;

                data = file->GetU16LE( high_angle_offset + 0x2 );
                //LOGGER->Log( "AnimationExtractor: data_y = 0x" + ToHexString( data, 4, '0' ) + ".\n" );
                if( ( flags & 2 ) == 0 )
                {
                    data = file->GetU8( data + frame );
                    //LOGGER->Log( "AnimationExtractor: flagged data_y = 0x" + ToHexString( data, 4, '0' ) + ".\n" );
                }
                raw_ry = ( data & 0xff ) << 4;

                data = file->GetU16LE( high_angle_offset + 0x4 );
                //LOGGER->Log( "AnimationExtractor: data_z = 0x" + ToHexString( data, 4, '0' ) + ".\n" );
                if( ( flags & 4 ) == 0 )
                {
                    data = file->GetU8( data + frame );
                    //LOGGER->Log( "AnimationExtractor: flagged data_z = 0x" + ToHexString( data, 4, '0' ) + ".\n" );
                }
                raw_rz = ( data & 0xff ) << 4;

                //LOGGER->Log( "AnimationExtractor: GET ROTATION frame = " + Ogre::StringConverter::toString( frame ) + ", bone = " + Ogre::StringConverter::toString( bone ) + ", flags = 0x" + ToHexString( flags, 4, '0' ) + ", high_rx = 0x" + ToHexString( raw_rx, 4, '0' ) + ", high_ry = 0x" + ToHexString( raw_ry, 4, '0' ) + ", high_rz = 0x" + ToHexString( raw_rz, 4, '0' ) + ".\n" );

                // if there is low range angles
                if( low_angle_offset != 0 )
                {
                    flags = file->GetU16LE( low_angle_offset + 0x6 );

                    u16 data = file->GetU16LE( low_angle_offset + 0x0 );
                    if( ( flags & 1 ) == 0 )
                    {
                        data = file->GetU8( data + frame / 2 );
                        if( frame & 1 )
                        {
                            data >>= 4;
                        }
                    }
                    raw_rx |= data & 0xf;

                    data = file->GetU16LE( low_angle_offset + 0x2 );
                    if( ( flags & 2 ) == 0 )
                    {
                        data = file->GetU8( data + frame / 2 );
                        if( frame & 1 )
                        {
                            data >>= 4;
                        }
                    }
                    raw_ry |= data & 0xf;

                    data = file->GetU16LE( low_angle_offset + 0x4 );
                    if( ( flags & 4 ) == 0 )
                    {
                        data = file->GetU8( data + frame / 2 );
                        if( frame & 1 )
                        {
                            data >>= 4;
                        }
                    }
                    raw_rz |= data & 0xf;

                    //LOGGER->Log( "AnimationExtractor: GET ROTATION frame = " + Ogre::StringConverter::toString( frame ) + ", bone = " + Ogre::StringConverter::toString( bone ) + ", low_flags = 0x" + ToHexString( flags, 4, '0' ) + ", total_rx = 0x" + ToHexString( raw_rx, 4, '0' ) + ", total_ry = 0x" + ToHexString( raw_ry, 4, '0' ) + ", total_rz = 0x" + ToHexString( raw_rz, 4, '0' ) + ".\n" );

                    low_angle_offset += 8;
                }

                high_angle_offset += 8;



                rx = ( raw_rx / 4096.0f ) * 360.0f;
                ry = ( raw_ry / 4096.0f ) * 360.0f;
                rz = ( raw_rz / 4096.0f ) * 360.0f;



                // assign data to bone
                bone1 = skeleton->getBone( bone * 2 + 2 );
                bone2 = skeleton->getBone( bone * 2 + 3 );
                if( frame == 0 )
                {
                    track1 = anim->createNodeTrack( bone * 2 + 2, bone1 );
                    track1->removeAllKeyFrames();
                    track2 = anim->createNodeTrack( bone * 2 + 3, bone2 );
                    track2->removeAllKeyFrames();

                }
                else
                {
                    track1 = anim->getNodeTrack( bone * 2 + 2 );
                    track2 = anim->getNodeTrack( bone * 2 + 3 );
                }
                frame1 = track1->createNodeKeyFrame( time );
                frame2 = track2->createNodeKeyFrame( time );
                Ogre::Vector3 translate = Ogre::Vector3( tx, ty, tz + skeleton_data[ bone ].length ) / 512.0f;
                //LOGGER->Log( "AnimationExtractor: frame = " + Ogre::StringConverter::toString( frame ) + ", bone = " + Ogre::StringConverter::toString( bone ) + ", translation = " + Ogre::StringConverter::toString( translate ) + ".\n" );
                frame1->setTranslate( translate );
                //mat.FromEulerAnglesXYZ( Ogre::Radian( Ogre::Degree( rx ) ), Ogre::Radian( Ogre::Degree( ry ) ), Ogre::Radian( Ogre::Degree( rz ) ) );
                //mat.FromEulerAnglesXZY( Ogre::Radian( Ogre::Degree( rx ) ), Ogre::Radian( Ogre::Degree( rz ) ), Ogre::Radian( Ogre::Degree( ry ) ) );
                //mat.FromEulerAnglesYZX( Ogre::Radian( Ogre::Degree( ry ) ), Ogre::Radian( Ogre::Degree( rz ) ), Ogre::Radian( Ogre::Degree( rx ) ) );
                //mat.FromEulerAnglesYXZ( Ogre::Radian( Ogre::Degree( ry ) ), Ogre::Radian( Ogre::Degree( rx ) ), Ogre::Radian( Ogre::Degree( rz ) ) );
                mat.FromEulerAnglesZYX( Ogre::Radian( Ogre::Degree( rz ) ), Ogre::Radian( Ogre::Degree( ry ) ), Ogre::Radian( Ogre::Degree( rx ) ) );
                //mat.FromEulerAnglesZXY( Ogre::Radian( Ogre::Degree( rz ) ), Ogre::Radian( Ogre::Degree( rx ) ), Ogre::Radian( Ogre::Degree( ry ) ) );
                //LOGGER->Log( "AnimationExtractor: frame = " + Ogre::StringConverter::toString( frame ) + ", bone = " + Ogre::StringConverter::toString( bone ) + ", rotation = " + Ogre::StringConverter::toString( rx ) + " " + Ogre::StringConverter::toString( ry ) + " " + Ogre::StringConverter::toString( rz ) + ".\n" );
                rot.FromRotationMatrix( mat );
                frame2->setRotation( rot );
            }

            time += 1.0f / fps;
        }

        delete file;
    }
}
