// ----------------------------------------------------------------------------
//
void SceneMovementAnimatorTask::genFanMovement( MovementAnimation& movement, ParticipantArray& participants )
{
    UINT pan_start = movement.m_pan_start;
    UINT pan_end = movement.m_pan_end;
    UINT tilt_start = movement.m_tilt_start;
    UINT tilt_end = movement.m_tilt_end;

    if ( pan_start > pan_end )
        swap<UINT>( pan_start, pan_end );

    UINT pan_increment = pan_end-pan_start;
    UINT pan_home = pan_start + (pan_increment/2);

    if ( participants.size() > 2 )
        pan_increment = (UINT)(((float)pan_increment)/(participants.size()-1)+0.5);

    for ( size_t particpant_index=0; particpant_index < participants.size(); ) {
        AngleList pan;
        AngleList tilt;
        ChannelValueArray dimmer;
        ChannelValueArray speed;

        // Go home
        tilt.push_back( tilt_start );
        pan.push_back( pan_home );
        dimmer.push_back( movement.m_backout_home_return ? 0 : 255 );
        speed.push_back( 0 );

        // Go to destination
        for ( UINT i=0; i < movement.m_dest_wait_periods; i++ ) {
            tilt.push_back( tilt_end );
            pan.push_back( pan_start+(pan_increment*particpant_index) );
            dimmer.push_back( 255 );
            speed.push_back( movement.m_speed );
        }

        // Go back home
        for ( UINT i=0; i < movement.m_home_wait_periods-1; i++ ) {
            tilt.push_back( tilt_start );
            pan.push_back( pan_home );
            dimmer.push_back( movement.m_backout_home_return ? 0 : 255 );
            speed.push_back( 0 );
        }

        if ( !movement.m_backout_home_return )          // TEMP fix for dimmer channel contention
            dimmer.clear();

        // Populate the channel arrays for the next group of fixtures
        populateChannelAnimations( participants, particpant_index, tilt, pan, dimmer, speed, 1, movement.m_run_once );
    }
}
// ----------------------------------------------------------------------------
// NOTE: Dimmer channel should contain 0 for full off and 255 for full on and
// no other values
//
void SceneMovementAnimatorTask::populateChannelAnimations(
    ParticipantArray& participants, size_t& particpant_index,
    AngleList& tilt, AngleList& pan, ChannelValueArray& dimmer,
    ChannelValueArray& speed, size_t group_size, bool run_once )
{
    size_t end = particpant_index + group_size;

    // If run once, add last entry to shut down the lights
    if ( run_once && dimmer.size() > 0 ) {
        dimmer.push_back( 0 );
    }

    ChannelAnimationStyle style( run_once ? CAM_LIST_ONCE : CAM_LIST );

    for ( ; particpant_index < end && particpant_index < participants.size(); particpant_index++ ) {
		channel_address pan_channel = participants[ particpant_index ].m_head.m_pan;
		channel_address tilt_channel = participants[ particpant_index ].m_head.m_tilt;
		channel_address dimmer_channel = participants[ particpant_index ].m_head.m_dimmer;
		channel_address speed_channel = participants[ particpant_index ].m_head.m_speed;
        UID actor_uid = participants[ particpant_index ].m_actor_uid;

        Fixture* pf = getActorRepresentative( actor_uid );
        if ( !pf )
            continue;

        if ( tilt_channel != INVALID_CHANNEL && tilt.size() ) {
            Channel* cp = pf->getChannel( tilt_channel );
            add( actor_uid, tilt_channel, style, anglesToValues( cp, tilt, cp->getMinAngle(), cp->getMaxAngle() ) );
        }

        if ( pan_channel != INVALID_CHANNEL && pan.size() ) {
            Channel* cp = pf->getChannel( pan_channel );
            add( actor_uid, pan_channel, style, anglesToValues( cp, pan, cp->getMinAngle(), cp->getMaxAngle() ) );
        }

        if ( speed_channel != INVALID_CHANNEL && speed.size() ) {
            add( actor_uid, speed_channel, style, speed );
        }

        if ( dimmer_channel != INVALID_CHANNEL && dimmer.size() ) {
            Channel* ch = pf->getChannel( dimmer_channel );
            STUDIO_ASSERT( ch, "Can't access dimmer channel %d on fixture %s", dimmer_channel, pf->getFullName() );

            BYTE low = ch->getDimmerLowestIntensity();
            BYTE high = ch->getDimmerHighestIntensity();

            // Replace all 255 dimmer high value with the fixture's dimmer value iff the actors value != 0
            SceneActor* actor = getActor( actor_uid );
            if ( actor && actor->getFinalChannelValue( pf->getUID(), dimmer_channel ) != 0 )
                high = actor->getFinalChannelValue( pf->getUID(), dimmer_channel );

            if ( low != 0 || high != 255 ) {                // Special case odd dimmer values
                for ( size_t i=0; i < dimmer.size(); i++ )
                    dimmer[i] = ( dimmer[i] == 255 ) ? high : low;
            }

            add( actor_uid, dimmer_channel, style, dimmer );
        }
    }
}
// ----------------------------------------------------------------------------
//
void SceneMovementAnimatorTask::genCoordinatesMovement( MovementAnimation& movement, ParticipantArray& participants )
{
    for ( size_t particpant_index=0; particpant_index < participants.size(); ) {
        AngleList pan;
        AngleList tilt;
        ChannelValueArray dimmer;
        ChannelValueArray speed;

        // Generate random locations within the tilt and pan bounds
        for ( UINT index=0; index < movement.m_coordinates.size(); index++ ) {
            UINT tilt_angle = movement.m_coordinates[index].m_tilt;
            UINT pan_angle = movement.m_coordinates[index].m_pan;

            if ( index > 0 && movement.m_backout_home_return ) {
                pan.push_back( pan_angle );
                tilt.push_back( tilt_angle );
                speed.push_back( 0 );
                dimmer.push_back( 0 );
            }

            for ( UINT wait=0; wait < movement.m_dest_wait_periods; wait++ ) {
                pan.push_back( pan_angle );
                tilt.push_back( tilt_angle );
                speed.push_back( movement.m_speed );
                dimmer.push_back( 255 );
            }
        }

        // Populate the channel arrays for the next group of fixtures
        populateChannelAnimations( participants, particpant_index, tilt, pan, dimmer, speed, movement.m_group_size, movement.m_run_once );
    }
}
// ----------------------------------------------------------------------------
//
void SceneMovementAnimatorTask::genRotateMovement( MovementAnimation& movement, ParticipantArray& participants )
{
    bool forward = true;				// Channel value population direction for alternate

    for ( size_t particpant_index=0; particpant_index < participants.size(); ) {
        AngleList pan;
        AngleList tilt;
        ChannelValueArray dimmer;
        ChannelValueArray speed;

        UINT pan_start = movement.m_pan_start;
        UINT pan_end = movement.m_pan_end;
        UINT tilt_start = movement.m_tilt_start;
        UINT tilt_end = movement.m_tilt_end;

        if ( !forward ) {
            swap<UINT>( pan_start, pan_end );
            swap<UINT>( tilt_start, tilt_end );
        }

        // Go home
        tilt.push_back( tilt_start );
        pan.push_back( pan_start );
        dimmer.push_back( movement.m_backout_home_return ? 0 : 255 );
        speed.push_back( 0 );

        // Go to destination
        for ( UINT i=0; i < movement.m_dest_wait_periods; i++ ) {
            tilt.push_back( tilt_end );
            pan.push_back( pan_end );
            dimmer.push_back( 255 );
            speed.push_back( movement.m_speed );
        }

        // Go back home (this wraps so account for the go home already added)
        for ( UINT i=0; i < movement.m_home_wait_periods-1; i++ ) {
            tilt.push_back( tilt_start );
            pan.push_back( pan_start );
            dimmer.push_back( movement.m_backout_home_return ? 0 : 255 );
            speed.push_back( 0 );
        }

        if ( !movement.m_backout_home_return )          // TEMP fix for dimmer channel contention
            dimmer.clear();

        // Populate the channel arrays for the next group of fixtures
        populateChannelAnimations( participants, particpant_index, tilt, pan, 
            dimmer, speed, movement.m_group_size, movement.m_run_once );

        if ( movement.m_alternate_groups )
            forward = !forward;
    }
}
// ----------------------------------------------------------------------------
//
void ScenePixelAnimator::initAnimation( AnimationTask* task, DWORD time_ms, BYTE* dmx_packet )
{
    m_animation_task = task;
    m_channel_animations.clear();

    typedef std::vector<Participant> ParticipantArray;

    ParticipantArray participants;

    // Determine which channels will be participating
    for ( UID actor_uid : populateActors() ) {
        Fixture* pf = m_animation_task->getActorRepresentative( actor_uid );
        if ( pf->getNumPixels() > 0 ) {
            participants.push_back( Participant( actor_uid, pf->getPixels() ) );
        }
    }

    if ( participants.size() > 0 ) {
        PixelEngine engine;

        if ( m_combine_fixtures ) {
            for ( Participant& p : participants )
                engine.loadPixels( p.m_actor_uid, p.m_pixels );

            generateEffect( engine );
        }
        else {
            for ( Participant& p : participants ) {
                engine.clear();
                engine.loadPixels( p.m_actor_uid, p.m_pixels );
                generateEffect( engine );
            }
        }
    }

    SceneChannelAnimator::initAnimation( task, time_ms, dmx_packet );
}
// ----------------------------------------------------------------------------
//
void SceneMovementAnimatorTask::generateProgram( AnimationDefinition* definition )
{
    SceneMovementAnimator* config = dynamic_cast< SceneMovementAnimator *>( definition );

    MovementAnimation& movement = config->movement();

    ParticipantArray participants;
    Head head;

    // Determine which actors will be participating
	for ( SceneActor& actor : getActors() ) {
        Fixture* pf = getActorRepresentative( actor.getActorUID() );
        if ( !pf || !pf->canMove() )
            continue;

        if ( movement.m_head_number == 0 ) { // All heads on fixture(s)
            for ( UINT head_number=1; head_number <= pf->getNumHeads(); head_number++ ) {
                if ( pf->getHead( head_number, head ) )
                    participants.emplace_back( actor.getActorUID(), head );                    
            }
        }
        else {                                 // Single head on fixture(s)
            if ( pf->getHead( movement.m_head_number, head ) )
                participants.emplace_back( actor.getActorUID(), head ); 
        }
    }

    STUDIO_ASSERT( movement.m_group_size >= 1, "Movement group size must be >= 1" );
    STUDIO_ASSERT( movement.m_home_wait_periods > 0 && movement.m_home_wait_periods < MAX_WAIT_PERIODS, 
        "Home wait periods must be between 1 and %d", MAX_WAIT_PERIODS );
    STUDIO_ASSERT( movement.m_dest_wait_periods > 0 && movement.m_dest_wait_periods < MAX_WAIT_PERIODS, 
        "Destination wait periods must be between 1 and %d", MAX_WAIT_PERIODS );

    // TODO: At some point, pre-compute the channel arrays once rather on each scene load as
    // that will be a problem for many fixtures/animations in a single scene.

    switch ( movement.m_movement_type ) {
        case MOVEMENT_RANDOM:							// Move to random locations
            genRandomMovement( movement, participants );
            break;

        case MOVEMENT_FAN:								// Fan beams
            genFanMovement( movement, participants );
            break;

        case MOVEMENT_ROTATE:							// Simple rotate at tilt angle
            genRotateMovement( movement, participants );
            break;

        case MOVEMENT_NOD:								// Simple up and down
            genNodMovement( movement, participants );
            break;

        case MOVEMENT_XCROSS:							// Cross fixture beams
            genXcrossMovement( movement, participants );
            break;

        case MOVEMENT_MOONFLOWER:						// Moonflower effect
            genMoonflowerMovement( movement, participants );
            break;

        case MOVEMENT_COORDINATES:						// Absolute coordinates effect
            genCoordinatesMovement( movement, participants );
            break;

        case MOVEMENT_SINEWAVE:						    // Sinewave movement
            genSineMovement( movement, participants );
            break;
    }
}
// ----------------------------------------------------------------------------
//
void SceneMovementAnimatorTask::genMoonflowerMovement( MovementAnimation& movement, ParticipantArray& participants )
{
    STUDIO_ASSERT( movement.m_height > 0, "Height must be a positive integer > 0" );
    if ( participants.size() == 0 )
        return;

    double fixture_angle_increment = 360 / participants.size();
    double start_angle = 0;

    for ( size_t particpant_index=0; particpant_index < participants.size(); ) {
        AngleList pan;
        AngleList tilt;
        ChannelValueArray dimmer;
        ChannelValueArray speed;

        UINT pan_start;
        UINT tilt_start;

        compute_pan_tilt( particpant_index, movement.m_height, movement.m_fixture_spacing, 
            movement.m_home_x, movement.m_home_y, pan_start, tilt_start );

        double angle = start_angle;

        for ( size_t i=0; i < movement.m_positions; i++ ) {
            // Go home
            tilt.push_back( tilt_start );
            pan.push_back( pan_start );

            double target_x = movement.m_home_x + movement.m_radius * cos( angle * 180 / M_PI );
            double target_y = movement.m_home_y + movement.m_radius * sin( angle * 180 / M_PI );

            //printf( "fixture %d: %f,%f to x,y=%f,%f angle=%f\n", particpant_index, home_x, home_y, target_x, target_y, angle );

            UINT pan_target;
            UINT tilt_target;

            compute_pan_tilt( particpant_index, movement.m_height, movement.m_fixture_spacing, 
                target_x, target_y, pan_target, tilt_target );

            //printf( "%d: pan_start=%d tilt_start=%d pan_end=%d tilt_end=%d\n", particpant_index, pan_start, tilt_start, pan_target, tilt_target);

            compute_fastest_path( pan_start, tilt_start, pan_target, tilt_target );

            //printf( "%d: pan_start=%d tilt_start=%d pan_end=%d tilt_end=%d\n", particpant_index, pan_start, tilt_start, pan_target, tilt_target);

            // Go to destination
            for ( UINT i=0; i < movement.m_dest_wait_periods; i++ ) {
                tilt.push_back( tilt_target );
                pan.push_back( pan_target );
            }

            // Go back home
            for ( UINT i=0; i < movement.m_home_wait_periods-1; i++ ) {
                tilt.push_back( tilt_start );
                pan.push_back( pan_start );
            }

            angle += movement.m_pan_increment;
            if ( angle > 360 ) 
                angle -= 360;
        }

        populateChannelAnimations( participants, particpant_index, tilt, pan, dimmer, speed, 1, movement.m_run_once );

        start_angle += fixture_angle_increment;
    }
}
// ----------------------------------------------------------------------------
//
void SceneMovementAnimatorTask::genSineMovement( MovementAnimation& movement, ParticipantArray& participants )
{
    std::vector<double> sinewave;
    for ( double angle=0.0; angle < 360.0; angle += movement.m_positions ) {
        double radians = angle * M_PI / 180.0;
        sinewave.push_back( sin(radians * 2) );
    }

    UINT start_angle = 0;
    UINT pan_increment = (UINT)(360.0 / sinewave.size() + .5);
    if ( pan_increment < 1 )
        return;

    for ( size_t particpant_index=0; particpant_index < participants.size(); ) {
        AngleList pan;
        AngleList tilt;
        ChannelValueArray dimmer;
        ChannelValueArray speed;

        UINT previous_pan = -1;
        UINT previous_tilt = -1;

        UINT range = abs( (long)(movement.m_tilt_end - movement.m_tilt_start) ) / 2;
        UINT tilt_base = std::min<UINT>( movement.m_tilt_end, movement.m_tilt_start ) + range;

        for (  UINT pan_angle=start_angle, sine_index=0; sine_index < sinewave.size(); pan_angle += pan_increment, sine_index++ ) {
            UINT tilt_angle = (UINT)((range * sinewave[sine_index]) + tilt_base);

            if ( previous_pan != pan_angle || previous_tilt != tilt_angle ) {
                for ( UINT wait=0; wait < movement.m_dest_wait_periods; wait++ ) {
                    pan.push_back( pan_angle );
                    tilt.push_back( tilt_angle );
                    speed.push_back( movement.m_speed );
                    dimmer.push_back( 255 );
                }

                previous_pan = pan_angle;
                previous_tilt = tilt_angle;
            }
        }

        // Wave back to home
        if ( pan.size() > 1 ) {
            size_t size = pan.size()-2;
            for ( size_t index=size; index > 0; index-- ) {
                pan.push_back( pan[index] );                // size-2 through 1
                tilt.push_back( tilt[size-index+1] );       // 1 through size-1
                speed.push_back( movement.m_speed );
                dimmer.push_back( 255 );
            }
        }

        start_angle += movement.m_pan_increment;

        if ( !movement.m_backout_home_return )          // TEMP fix for dimmer channel contention
            dimmer.clear();

        // Populate the channel arrays for the next group of fixtures
        populateChannelAnimations( participants, particpant_index, tilt, pan, dimmer, speed, movement.m_group_size, movement.m_run_once );
    }
}
void SceneMovementAnimatorTask::genRandomMovement( MovementAnimation& movement, ParticipantArray& participants )
{
    STUDIO_ASSERT( movement.m_positions > 0 && movement.m_positions < MAX_RANDOM_POSITIONS, 
        "Random postions should be between 1 and %d", MAX_RANDOM_POSITIONS );

    for ( size_t particpant_index=0; particpant_index < participants.size(); ) {
        Fixture* pf = getActorRepresentative( participants[ particpant_index ].m_actor_uid );
        if ( !pf )
            continue;

        UINT tilt_start = movement.m_tilt_start, tilt_end = movement.m_tilt_end;
        UINT pan_start = movement.m_pan_start, pan_end = movement.m_pan_end;

        AngleList pan;
        AngleList tilt;
        ChannelValueArray dimmer;
        ChannelValueArray speed;

        channel_address tilt_channel = participants[ particpant_index ].m_head.m_tilt;
        if ( tilt_channel != INVALID_CHANNEL ) {
            Channel* cp = pf->getChannel( tilt_channel );
            tilt_start = std::max<UINT>( tilt_start, cp->getMinAngle() );
            tilt_end = std::min<UINT>( tilt_end, cp->getMaxAngle() );
        }

        channel_address pan_channel = participants[ particpant_index ].m_head.m_pan;
        if ( pan_channel != INVALID_CHANNEL ) {
            Channel* cp = pf->getChannel( pan_channel );
            pan_start = std::max<UINT>( pan_start, cp->getMinAngle() );
            pan_end = std::min<UINT>( pan_end, cp->getMaxAngle() );
        }

        // Generate random locations within the tilt and pan bounds
        for ( UINT generate=0; generate < movement.m_positions; generate++ ) {
            UINT tilt_angle = random_angle( tilt_start, tilt_end );
            UINT pan_angle = random_angle( pan_start, pan_end );

            UINT wait_periods = movement.m_dest_wait_periods;

            // No light during movement
            if ( movement.m_backout_home_return && wait_periods > 1 ) {
                pan.push_back( pan_angle );
                tilt.push_back( tilt_angle );
                speed.push_back( 0 );
                dimmer.push_back( 0 );
                wait_periods--;
            }

            for ( UINT wait=0; wait < wait_periods; wait++ ) {
                pan.push_back( pan_angle );
                tilt.push_back( tilt_angle );
                speed.push_back( movement.m_speed );
                dimmer.push_back( 255 );
            }
        }

        if ( !movement.m_backout_home_return )          // TEMP fix for dimmer channel contention
            dimmer.clear();

        // Populate the channel arrays for the next group of fixtures
        populateChannelAnimations( participants, particpant_index, tilt, pan, dimmer, speed, movement.m_group_size, movement.m_run_once );
    }
}
// ----------------------------------------------------------------------------
//
void SceneMovementAnimatorTask::genNodMovement( MovementAnimation& movement, ParticipantArray& participants )
{
    bool forward = true;				// Channel value population direction for alternate

    for ( size_t particpant_index=0; particpant_index < participants.size(); ) {
        AngleList pan;
        AngleList tilt;
        ChannelValueArray dimmer;
        ChannelValueArray speed;

        UINT pan_start = movement.m_pan_start;
        UINT pan_end = movement.m_pan_end;
        UINT tilt_start = movement.m_tilt_start;
        UINT tilt_end = movement.m_tilt_end;

        if ( !forward ) {
            swap<UINT>( tilt_start, tilt_end );
        }

        // Go home
        //tilt.push_back( tilt_start );
        //pan.push_back( pan_start );
        //dimmer.push_back( movement.m_backout_home_return ? 0 : 255 );
        //speed.push_back( 0 );

        if ( pan_start > pan_end )
            swap<UINT>( pan_start, pan_end );

        for ( int pan_angle=pan_start; pan_angle <= (int)pan_end; pan_angle += movement.m_pan_increment ) {
            // Go to destination
            for ( UINT i=0; i < movement.m_dest_wait_periods; i++ ) {
                tilt.push_back( tilt_end );
                pan.push_back( pan_angle );
                dimmer.push_back( 255 );
                speed.push_back( movement.m_speed );
            }

            // Go back home
            for ( UINT i=0; i < movement.m_home_wait_periods; i++ ) {
                tilt.push_back( tilt_start );
                pan.push_back( pan_angle );
                dimmer.push_back( movement.m_backout_home_return ? 0 : 255 );
                speed.push_back( movement.m_speed );
            }

            if ( movement.m_pan_increment == 0 )        // If not panning, just need one of the above
                break;
        }

        if ( !movement.m_backout_home_return && movement.m_pan_increment > 0 ) {
            for ( int pan_angle=pan_end-=movement.m_pan_increment; pan_angle >= (int)pan_start; pan_angle-=movement.m_pan_increment ) {
                // Go to destination
                for ( UINT i=0; i < movement.m_dest_wait_periods; i++ ) {
                    tilt.push_back( tilt_end );
                    pan.push_back( pan_angle );
                    dimmer.push_back( 255 );
                    speed.push_back( movement.m_speed );
                }

                // Go back home
                for ( UINT i=0; i < movement.m_home_wait_periods; i++ ) {
                    tilt.push_back( tilt_start );
                    pan.push_back( pan_angle );
                    dimmer.push_back( 255 );
                    speed.push_back( movement.m_speed );
                }
            }
        }

        if ( !movement.m_backout_home_return )          // TEMP fix for dimmer channel contention
            dimmer.clear();

        // Populate the channel arrays for the next group of fixtures
        populateChannelAnimations( participants, particpant_index, tilt, pan, dimmer, speed, movement.m_group_size, movement.m_run_once );

        if ( movement.m_alternate_groups )
            forward = !forward;
    }
}