use std::{cmp::{max, min}, error::Error, fs::File, io::Write};
use samplerate::convert;
use crate::reskit::{soundtrack::{formats::dmf::{DmfModule, ECHO_EWF_SAMPLE_RATE}, types::{InstrumentType, SampleFormat, Channel, PatternRow, Effect, CombinedAssets, Instrument, Sample}}, utility::{print_warning, Ring, symbol_to_pascal}};
use super::engine::{EchoFormat, ESF_FM_1, ESF_FM_2, ESF_FM_3, ESF_FM_4, ESF_FM_5, ESF_FM_6, ESF_PSG_1, ESF_PSG_2, ESF_PSG_3, ESF_PSG_4, EchoEvent, get_events_for_row, compact_delays, ESF_SET_LOOP, ESF_GO_TO_LOOP, ESF_STOP, EchoArtifact};

impl EchoArtifact for Instrument {

    fn to_bytes( &self ) -> Result<Vec<u8>, Box<dyn Error>> {
        let mut data: Vec<u8> = Vec::new();

        match &self.instrument_type {
            InstrumentType::Fm2612( settings ) => {
                // Create feedback + algorithm byte
                let alg_fb: u8 = ( settings.fb << 3 ) | settings.alg;

                // The operators are laid out as FmOperator objects in order 1 -> 3 -> 2 -> 4
                // EIF instrument layout below
                let mut mult_dt: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
                let mut tl: [u8; 4]      = [ 0x00, 0x00, 0x00, 0x00 ];
                let mut ar_rs: [u8; 4]   = [ 0x00, 0x00, 0x00, 0x00 ];
                let mut dr_am: [u8; 4]   = [ 0x00, 0x00, 0x00, 0x00 ];
                let mut sr: [u8; 4]      = [ 0x00, 0x00, 0x00, 0x00 ];
                let mut rr_sl: [u8; 4]   = [ 0x00, 0x00, 0x00, 0x00 ];
                let mut ssg_eg: [u8; 4]  = [ 0x00, 0x00, 0x00, 0x00 ];

                // Likewise, "operators" is laid out 1 -> 3 -> 2 -> 4
                for i in 0..4 {
                    let dt: u8 = match settings.operators[ i ].dt {
                        0 => 0b000,
                        1 => 0b001,
                        2 => 0b010,
                        3 => 0b011,
                        -1 => 0b101,
                        -2 => 0b110,
                        -3 => 0b111,
                        _ => return Err( "invalid file: invalid value given for dt" )?
                    };

                    // https://plutiedev.com/ym2612-registers
                    // https://github.com/sikthehedgehog/Echo/blob/master/doc/eif.txt
                    mult_dt[ i ] = ( dt << 4 ) | settings.operators[ i ].mult;
                    tl[ i ]      = settings.operators[ i ].tl;
                    ar_rs[ i ]   = ( settings.operators[ i ].rs << 6 ) | settings.operators[ i ].ar;
                    dr_am[ i ]   = ( settings.operators[ i ].am << 7 ) | settings.operators[ i ].dr;
                    sr[ i ]      = settings.operators[ i ].d2r;     // "Sometimes also called 'second decay rate' (D2R)."
                    rr_sl[ i ]   = ( settings.operators[ i ].sl << 4 ) | settings.operators[ i ].rr;

                    if settings.operators[ i ].ssg_mode > 0 {
                        print_warning( &format!( "SSG-EG mode set on instrument {}, operator {}. this operator may not work on clone hardware or certain emulators", self.name, i ) );
                    }

                    ssg_eg[ i ]  = settings.operators[ i ].ssg_mode;
                }

                data.push( alg_fb );
                data.extend( mult_dt );
                data.extend( tl );
                data.extend( ar_rs );
                data.extend( dr_am );
                data.extend( sr );
                data.extend( rr_sl );
                data.extend( ssg_eg );
            }
            InstrumentType::PsgDcsg( settings ) => {
                // Echo uses volume and arpeggio envelopes
                // Noise envelopes are usable but cannot be articulated in an instrument.
                // For use of the noise envelope, use ESF $0Bnn/$3Bnn and $3Ann in the stream.

                // comments using example: 20 vol values, 6 arp values, both repeat from the beginning

                // generate the initial portion (always). this is just an expansion
                // of the envelopes, without loops, into two Vec<u8>'s.
                // 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
                // 01 02 03 04 05 06 -- -- -- -- -- -- -- -- -- -- -- -- -- --
                let mut volume_envelope: Vec<u8> = settings.volume.envelope.iter().map( | long | *long as u8 ).collect();
                let mut arpeggio_envelope: Vec<i8> = settings.arpeggio.envelope.iter().map( | long | *long as i8 ).collect();

                // Validations (this makes my life easier when dmf format changes again)
                if volume_envelope.is_empty() {
                    // Push one max volume if there is no volume defined in this instrument
                    volume_envelope.push( 0x0F );
                }
                if arpeggio_envelope.is_empty() {
                    // Push one byte of no arpeggio shift if no arpeggio is defined in this instrument
                    arpeggio_envelope.push( 0x00 );
                }

                // now take a slice of the repeatable portion of the smaller array,
                // and use it to expand the smaller array to the same size as the
                // larger array. if there is no repeatable portion, take a one-element
                // slice of the last element in the array.
                // 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
                // 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02
                let mut volume_repeat_pattern: Ring<u8> = Ring::create(
                    if let Some( loop_at ) = settings.volume.loop_at {
                        Vec::from( &volume_envelope[ loop_at.. ] )
                    } else {
                        vec![ volume_envelope[ volume_envelope.len() - 1 ] ]
                    },
                    None
                );
                let mut arpeggio_repeat_pattern: Ring<i8> = Ring::create(
                    if let Some( loop_at ) = settings.arpeggio.loop_at {
                        Vec::from( &arpeggio_envelope[ loop_at.. ] )
                    } else {
                        vec![ arpeggio_envelope[ arpeggio_envelope.len() - 1 ] ]
                    },
                    None
                );

                if volume_envelope.len() < arpeggio_envelope.len() {
                    let difference = arpeggio_envelope.len() - volume_envelope.len();
                    for _ in 0..difference {
                        volume_envelope.push(
                            volume_repeat_pattern.next().expect( "fatal: internal error (volume_repeat_pattern)" )
                        );
                    }
                } else if arpeggio_envelope.len() < volume_envelope.len() {
                    let difference = volume_envelope.len() - arpeggio_envelope.len();
                    for _ in 0..difference {
                        arpeggio_envelope.push(
                            arpeggio_repeat_pattern.next().expect( "fatal: internal error (arpeggio_repeat_pattern)" )
                        );
                    }
                }

                let interval_size = max( volume_repeat_pattern.len(), arpeggio_repeat_pattern.len() );

                // now, you generate the repeating portion using the two repeat_pattern iterators.
                // each segment at a time is generated by the size of the larger array.
                // generate it until the first repeat pattern is regenerated.
                // 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 ..       (volume repeats from beginning)
                // 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 ..       (arpeggio repeats up from where it left off)

                // 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 ..
                // 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 ..

                // 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 ..
                // 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02 ..
                let mut volume_repeat: Vec<u8> = Vec::new();
                let mut arpeggio_repeat: Vec<i8> = Vec::new();

                let mut initial_volume_repeat: Vec<u8> = Vec::new();
                let mut initial_arpeggio_repeat: Vec<i8> = Vec::new();
                for _ in 0..interval_size {
                    initial_volume_repeat.push( volume_repeat_pattern.next().expect( "fatal: internal error (volume_repeat_pattern)" ) );
                    initial_arpeggio_repeat.push( arpeggio_repeat_pattern.next().expect( "fatal: internal error (arpeggio_repeat_pattern)" ) );
                }

                volume_repeat.extend( &initial_volume_repeat );
                arpeggio_repeat.extend( &initial_arpeggio_repeat );

                loop {
                    // Guard against OOM
                    if volume_repeat.len() > 65535 {
                        return Err( "eef error: psg instrument repeat period too long (try modifying repeating envelopes to have evenly-divisible parameters)" )?
                    }

                    let mut volume_repeat_period: Vec<u8> = Vec::new();
                    let mut arpeggio_repeat_period: Vec<i8> = Vec::new();
                    for _ in 0..interval_size {
                        volume_repeat_period.push( volume_repeat_pattern.next().expect( "fatal: internal error (volume_repeat_pattern)" ) );
                        arpeggio_repeat_period.push( arpeggio_repeat_pattern.next().expect( "fatal: internal error (arpeggio_repeat_pattern)" ) );
                    }

                    // End loop once the pattern begins repeating
                    if initial_volume_repeat == volume_repeat_period && initial_arpeggio_repeat == arpeggio_repeat_period {
                        break;
                    } else {
                    // If it doesn't repeat, append and generate another period
                        volume_repeat.append( &mut volume_repeat_period );
                        arpeggio_repeat.append( &mut arpeggio_repeat_period );
                    }
                }

                // Now generate the Echo EEF envelope from volume_envelope/volume_repeat + arpeggio_envelope/arpeggio_repeat
                let mut envelope: Vec<u8> = vec![];
                for i in 0..volume_envelope.len() {
                    envelope.push( get_eef_volume( volume_envelope[ i ] )? | get_eef_shift( arpeggio_envelope[ i ] )? );
                }

                // Echo begin loop command
                envelope.push( 0xFE );

                for i in 0..volume_repeat.len() {
                    envelope.push( get_eef_volume( volume_repeat[ i ] )? | get_eef_shift( arpeggio_repeat[ i ] )? );
                }

                // Echo end loop command
                envelope.push( 0xFF );

                data.extend( envelope.into_iter() );
            }
        }

        Ok( data )
    }

}

impl EchoArtifact for Sample {

    fn to_bytes( &self ) -> Result<Vec<u8>, Box<dyn Error>> {
        // Amplify data in original sample
        let data: Vec<i16> = self.data
                                    .iter()
                                    .map( | pcm_sample | {
                                        let big_pcm_sample: i32 = min( ( *pcm_sample as i32 ) + self.amp as i32, i16::MAX as i32 );

                                        big_pcm_sample as i16
                                    } )
                                    .collect();

        // Convert to f32 for various library operations
        let data: Vec<f32> = data
            .into_iter()
            .map( | pcm_sample | {
                if let SampleFormat::Bits8 = self.bitrate {
                    ( pcm_sample as u8 ) as f32 / 255.0
                } else {
                    pcm_sample as f32 / 32767.0
                }
            } )
            .collect();

        // TODO: Adjust pitch using crate pitch_shift
        if self.pitch > 0 {
            print_warning( "pitch shift not yet implemented for pcm samples. your drums/voice samples/etc may sound a bit funny" );
        }

        // Use libsamplerate to resample from whatever it is now to 10650Hz, the sample rate
        // required by Echo sound engine EWF samples.
        let data: Vec<f32> = convert( self.rate, ECHO_EWF_SAMPLE_RATE, 1, samplerate::ConverterType::SincBestQuality, &data )?;

        // Convert from f32 back to i16, then downconvert to u8
        let mut data: Vec<u8> = data
            .into_iter()
            .map( | pcm_sample | {
                let pcm_sample = ( ( pcm_sample + 1.0 ) * 128.0 ) as u8;

                // Do not end stream prematurely (EWF format uses 0xFF to end stream)
                if pcm_sample == 0xFF {
                    0xFE
                } else {
                    pcm_sample
                }
            } )
            .collect();

        // Terminate stream
        data.push( 0xFF );

        Ok( data )
    }

}

impl EchoArtifact for CombinedAssets {

    fn to_bytes( &self ) -> Result<Vec<u8>, Box<dyn Error>> {
        // Write Echo ASM file that includes all the instruments
        let mut echo_asm: String = format!( "; Echo (esf) instruments and sequences source file\n; Generated by reskit v{}\n\n", env!( "CARGO_PKG_VERSION" ) );
        let mut index = 0;

        for soundtrack_path in &self.sequences {
            echo_asm += &format!( "{}:\n", symbol_to_pascal( soundtrack_path ) );
            echo_asm += &format!( "\tincbin '{}'\n\n", soundtrack_path );
        }

        echo_asm += &format!( "Instr_List:\n" );

        let instrument_paths: Vec<String> = self.instruments.iter()
            .map( | instrument |
                format!(
                    "Instrument{}_{}.{}",
                    if instrument.name.is_empty() {
                        format!( "_NoName" )
                    } else {
                    format!( "_{}", &instrument.name )
                    },
                    {
                        let given = index;
                        index += 1;
                        given
                    },
                    match &instrument.instrument_type {
                        InstrumentType::Fm2612(_) => "eif",
                        InstrumentType::PsgDcsg(_) => "eef"
                    }
                )
            )
            .collect();

        let sample_paths: Vec<String> = self.samples.iter()
            .map( | sample |
                format!(
                    "Sample{}_{}.ewf",
                    if sample.name.is_empty() {
                        format!( "_NoName" )
                    } else {
                    format!( "_{}", &sample.name )
                    },
                    {
                        let given = index;
                        index += 1;
                        given
                    }
                )
            )
            .collect();

        for instrument_path in &instrument_paths {
            echo_asm += &format!( "\tEcho_ListEntry Instr_{}\n", symbol_to_pascal( instrument_path ) );
        }
        for sample_path in &sample_paths {
            echo_asm += &format!( "\tEcho_ListEntry Sample_{}\n", symbol_to_pascal( sample_path ) );
        }

        echo_asm += &format!( "\tEcho_ListEnd\n\n" );

        for i in 0..self.instruments.len() {
            let instrument = &self.instruments[ i ];
            let instrument_path = &instrument_paths[ i ];
            let instrument_total_path = format!( "{}{}", self.output_prefix, instrument_path );

            echo_asm += &format!( "Instr_{}:\n", symbol_to_pascal( instrument_path ) );
            echo_asm += &format!( "\tincbin '{}'\n\n", instrument_total_path );

            println!( "Writing sequence artifact {}", instrument_total_path );
            let instrument_data = instrument.to_bytes()?;
            let mut file = File::create( instrument_total_path )?;
            file.write_all( &instrument_data )?;
        }

        for i in 0..self.samples.len() {
            let sample = &self.samples[ i ];
            let sample_path = &sample_paths[ i ];
            let sample_total_path = format!( "{}{}", self.output_prefix, sample_path );

            echo_asm += &format!( "Sample_{}:\n", symbol_to_pascal( sample_path ) );
            echo_asm += &format!( "\tincbin '{}'\n\n", sample_total_path );

            println!( "Writing sequence artifact {}", sample_total_path );
            let sample_data = sample.to_bytes()?;
            let mut file = File::create( sample_total_path )?;
            file.write_all( &sample_data )?;
        }

        Ok( echo_asm.into_bytes() )
    }

}

impl EchoFormat for DmfModule {

    fn get_esf( &self, combined_assets: &CombinedAssets ) -> Result<Vec<u8>, Box<dyn Error>> {
        let mut esf: Vec<u8> = Vec::new();

        // Write deflemask rows one at a time.
        // Final pass will combine delays into single delay events
        // to save space on the cartridge.

        // ESF ticks are not the same as Deflemask ticks!
        // A series of events fit into a single ESF tick, which ends
        // upon the next delay event or stop event.
        // Use instrument changes wisely, as these are expensive register writes.
        // PCM playback stalls the stream pipeline as well so use wisely.

        // FM1 -> SN4 channel states (used to compute effects and to aid in not duplicating instrument sets)
        // Since trackers like to set the same instrument each time a note on is played...
        let mut channels: [Channel; 10] = [
            Channel::new( ESF_FM_1 ), Channel::new( ESF_FM_2 ), Channel::new( ESF_FM_3 ), Channel::new( ESF_FM_4 ), Channel::new( ESF_FM_5 ), Channel::new( ESF_FM_6 ),
            Channel::new( ESF_PSG_1 ), Channel::new( ESF_PSG_2 ), Channel::new( ESF_PSG_3 ), Channel::new( ESF_PSG_4 )
        ];

        // Iterate for each row, for each channel
        // Recall items are stored as self.channel_patterns[ channel ][ row_number ]
        let mut all_events: Vec<EchoEvent> = Vec::new();
        let total_rows: u32 = self.channel_patterns[ 0 ].len() as u32;
        let mut ignore_row_counter: u32 = 0;
        for row_number in 0..total_rows {
            if ignore_row_counter > 0 {
                // Ignore these rows while this is set (for Effect::JumpToNextPattern)
                ignore_row_counter -= 1;
            } else {
                let mut columns: Vec<&PatternRow> = Vec::new();
                for channel in 0..10 {
                    columns.push( &self.channel_patterns[ channel ][ row_number as usize ] );
                }

                let events_this_row: Vec<EchoEvent> = get_events_for_row(
                    &mut channels,
                    &self.instruments,
                    &self.samples,
                    &combined_assets,
                    columns.clone(),
                    if row_number % 2 == 0 {
                        self.speed_a * self.time_base
                    } else {
                        self.speed_b * self.time_base
                    }
                )?;

                // Is this the pattern matrix row that we repeat to?
                // If so, insert an ESF_LOOP_SET here.
                if let Some( repeat_row ) = self.loop_at {
                    if row_number == repeat_row {
                        all_events.push( vec![ ESF_SET_LOOP ] );
                    }
                }

                // Transfer ESF events to the main stream
                all_events.extend( events_this_row );

                // Was there an Effect::JumpToNextPattern here? If so, set a counter to ignore the next n rows
                // Use the first one encountered (u r big dum dum if you set more than one in a single row, that DOES NOT MAKE SENSE stahp)
                for column in columns {
                    if let Some( jump_effect ) = column.effects.iter().find( | effect | matches!( effect, Effect::JumpToNextPattern{ start_row: _ } ) ) {
                        if let Effect::JumpToNextPattern { start_row } = jump_effect {
                            let rows_elapsed = row_number % self.rows_per_pattern;
                            let remaining_rows_in_pattern = self.rows_per_pattern - rows_elapsed - 1;
                            ignore_row_counter = remaining_rows_in_pattern + *start_row as u32;
                            break;
                        } else {
                            return Err( "get_esf: wtf happened here??" )?
                        }
                    }
                }
            }
        }

        // Compact sequences of delays to save rom space
        let all_events = compact_delays( all_events )?;
        for event in all_events {
            esf.extend( event );
        }

        esf.push( if let None = self.loop_at { ESF_STOP } else { ESF_GO_TO_LOOP } );   // Terminate the stream

        Ok( esf )
    }

}

fn get_eef_volume( dmf_volume: u8 ) -> Result<u8, Box<dyn Error>> {
    Ok( min( 0x0F - dmf_volume, 0x0F ) )
}

fn get_eef_shift( dmf_shift: i8 ) -> Result<u8, Box<dyn Error>> {
    // Clamp to [-12, 12]
    let as_i8: i8 = max( -12, min( dmf_shift, 12 ) );

    Ok(
        match as_i8 {
            0 => 0x00,

            1 => 0x10,                      -1 => 0x80,
            2 => 0x20,                      -2 => 0x90,
            3 => 0x30,                      -3 => 0xA0,
            4 => 0x40,                      -4 => 0xB0,
            5 | 6 => 0x50,                  -5 | -6 => 0xC0,
            7 | 8 => 0x60,                  -7 | -8 => 0xD0,
            9 | 10 | 11 | 12 => 0x70,       -9 | -10 | -11 | -12 => 0xE0,

            _ => return Err( "internal error: arpeggio shift out of defined range" )?
        }
    )
}