use std::{error::Error, fs::File, io::Read, convert::TryInto, collections::HashMap};
use flate2::read::ZlibDecoder;
use linked_hash_set::LinkedHashSet;
use crate::reskit::{utility::{get_string, get_u8, skip, get_u32, get_i8, get_i32, get_u16, get_i16, print_info, sanitize_string}, soundtrack::types::{SampleFormat, PsgEnvelope, Note, Sample, PsgSettings, PatternRow, Effect, DcsgChannelMode, NoiseType, Instrument, InstrumentType, Fm2612Operator, Fm2612Settings, Channel, CombinedAssets}};

const DMF_MAGIC_NUMBER: &'static str = ".DelekDefleMask.";
const DMF_SUPPORTED_VERSION: u8 = 27;
const DMF_MD: u8 = 0x02;
const DMF_MD_ENHANCED_CH3: u8 = 0x42;
pub const ECHO_EWF_SAMPLE_RATE: u32 = 10650;

const DMF_EFFECT_ARPEGGIO: u8           = 0x00;
const DMF_EFFECT_PORTAMENTO_UP: u8      = 0x01;
const DMF_EFFECT_PORTAMENTO_DOWN: u8    = 0x02;
const DMF_EFFECT_PORTA_TO_NOTE: u8      = 0x03;
const DMF_EFFECT_SET_PANNING: u8        = 0x08;
const DMF_EFFECT_POSITION_JUMP: u8      = 0x0B;
const DMF_EFFECT_PATTERN_BREAK: u8      = 0x0D;
const DMF_EFFECT_DAC_ENABLE: u8         = 0x17;
const DMF_EFFECT_PSG_NOISE_MODE: u8     = 0x20;
const DMF_EFFECT_NOTE_CUT: u8           = 0xEC;

#[derive(Debug)]
pub enum FrameMode {
    Ntsc,
    Pal,
    Custom( u8, u8, u8 )
}

pub struct DmfModule {
    pub path: String,
    platform: u8,
    version: u8,
    pub time_base: u8,
    pub speed_a: u8,
    pub speed_b: u8,
    frame_mode: FrameMode,
    pub rows_per_pattern: u32,
    pattern_matrix: Vec<Vec<u16>>,
    pub loop_at: Option<u32>,
    pub channel_patterns: Vec<Vec<PatternRow>>,
    pub instruments: Vec<Instrument>,
    pub samples: Vec<Sample>
}

impl DmfModule {

    /**
     * Build the combined instrument list across a list of Deflemask modules. When assigning instruments to tracks,
     * there will be a two step process. First, get the index into the module's list of instruments. Then, find that
     * instrument in the combined instrument list (by simple equality, even) and use _that_ index instead.
     */
    pub fn get_combined_assets_list( output_prefix: String, modules: &Vec<DmfModule> ) -> Result<CombinedAssets, Box<dyn Error>> {
        let mut instruments: Vec<Instrument> = Vec::new();
        let mut samples: Vec<Sample> = Vec::new();

        for module in modules {
            for instrument in &module.instruments {
                if !instruments.contains( instrument ) {
                    instruments.push( instrument.clone() );
                }
            }

            for sample in &module.samples {
                if !samples.contains( sample ) {
                    samples.push( sample.clone() );
                }
            }
        }

        Ok( CombinedAssets { output_prefix, sequences: Vec::new(), instruments, samples } )
    }

    pub fn from_file( path: &str ) -> Result<DmfModule, Box<dyn Error>> {
        let mut file = File::open( path )?;
        let mut compressed: Vec<u8> = Vec::new();
        file.read_to_end( &mut compressed )?;

        let mut inflator = ZlibDecoder::new( &compressed[..] );
        let mut bytes: Vec<u8> = Vec::new();
        inflator.read_to_end( &mut bytes )?;

        let mut bytes = bytes.iter();

        if get_string( bytes.by_ref(), 16 )? != DMF_MAGIC_NUMBER {
            return Err( format!( "invalid file: missing DMF header" ) )?
        }

        print_info( ".DelekDefleMask. DMF file" );

        let version = get_u8( bytes.by_ref() )?;
        if version != DMF_SUPPORTED_VERSION {
            return Err( format!( "invalid file: unsupported version: {}", version ) )?
        }

        let platform = get_u8( bytes.by_ref() )?;
        match platform {
            DMF_MD => print_info( "Mode:\tSega Megadrive" ),
            DMF_MD_ENHANCED_CH3 => return Err( "Extended Ch. 3 mode not yet supported (coming soon!) - if your sequence does not use Extended Ch. 3 mode, try re-exporting as standard mode." )?,
            _ => return Err( "invalid file: invalid console format" )?
        };

        let song_name_len = get_u8( bytes.by_ref() )?;
        print_info( &format!( "Song name:\t{}", sanitize_string( get_string( bytes.by_ref(), song_name_len.into() )? ) ) );

        let song_author_len = get_u8( bytes.by_ref() )?;
        print_info( &format!( "Song author:\t{}", sanitize_string( get_string( bytes.by_ref(), song_author_len.into() )? ) ) );

        // Burn 2 bytes, don't care about the highlight patterns
        skip( bytes.by_ref(), 2 );

        // DMF format appears to subtract by 1 from the base time shown in the UI
        // Base time is multiplied by speed_a and speed_b so setting it to 1 and
        // getting 0 doesn't make any sense.
        let time_base = get_u8( bytes.by_ref() )? + 1;
        let speed_a = get_u8( bytes.by_ref() )?;
        let speed_b = get_u8( bytes.by_ref() )?;

        print_info( &format!( "Time base:\t{}", time_base ) );
        print_info( &format!( "Ticks/Even:\t{}", speed_a ) );
        print_info( &format!( "Ticks/Odd:\t{}", speed_b ) );

        let frame_mode = match get_u8( bytes.by_ref() )? {
            0 => FrameMode::Pal,
            1 => FrameMode::Ntsc,
            _ => return Err( "invalid file: invalid frame mode" )?
        };

        let custom_frame_mode = get_u8( bytes.by_ref() )? == 1;
        let custom_frame_mode_hz_1 = get_u8( bytes.by_ref() )?;
        let custom_frame_mode_hz_2 = get_u8( bytes.by_ref() )?;
        let custom_frame_mode_hz_3 = get_u8( bytes.by_ref() )?;

        let frame_mode = if custom_frame_mode {
            FrameMode::Custom( custom_frame_mode_hz_1, custom_frame_mode_hz_2, custom_frame_mode_hz_3 )
        } else {
            frame_mode
        };

        print_info( &format!( "Frame mode:\t{:?}", frame_mode ) );

        let rows_per_pattern = get_u32( bytes.by_ref() )?;

        print_info( &format!( "Rows/pattern:\t{}", rows_per_pattern ) );

        let patterns_count = get_u8( bytes.by_ref() )?;

        print_info( &format!( "Patterns:\t{}\n", patterns_count ) );

        print_info( "Pattern Matrix:" );
        let system_total_channels = if platform == DMF_MD {
            print_info( "FM1  FM2  FM3  FM4  FM5  FM6  SN1  SN2  SN3  SN4" );
            10
        } else {
            print_info( "FM1  FM2  OP1  OP2  OP3  OP4  FM4  FM5  FM6  SN1  SN2  SN3  SN4" );
            13
        };

        let mut pattern_matrix: Vec<Vec<u16>> = vec![vec![0; system_total_channels]; patterns_count.into()];
        for channel in 0..system_total_channels {
            for pattern in 0..patterns_count {
                let channel: usize = channel.into();
                let pattern: usize = pattern.into();
                pattern_matrix[ pattern ][ channel ] = get_u16( bytes.by_ref() )?;
            }
        }

        for pattern in &pattern_matrix {
            let mut total_string: String = String::new();
            for channel in pattern {
               total_string += &format!( "{:#04X?} ", channel );
            }
            print_info( &total_string );
        }

        print_info( "\n" );

        let mut instruments: Vec<Instrument> = Vec::new();
        let instrument_count = get_u8( bytes.by_ref() )?;

        print_info( &format!( "Instruments:\t{}", instrument_count ) );

        for i in 0..instrument_count {
            let name_len = get_u8( bytes.by_ref() )?;
            let name = sanitize_string( get_string( bytes.by_ref(), name_len.into() )? );

            print_info( &format!( "Instrument {}:\t{}", i, name ) );

            let mode = get_u8( bytes.by_ref() )?;
            let instrument_type = match mode {
                0 => {
                    print_info( "Psg Instrument" );

                    // Volume envelope
                    let volume = {
                        let size = get_u8( bytes.by_ref() )?;
                        let mut envelope: Vec<u32> = Vec::new();

                        for _i in 0..size {
                            envelope.push( get_u32( bytes.by_ref() )? );
                        }

                        let loop_at: Option<usize> = if size > 0 {
                            let loop_position = get_i8( bytes.by_ref() )?;
                            if loop_position != -1 {
                                Some( loop_position.try_into()? )
                            } else {
                                None
                            }
                        } else {
                            None
                        };

                        print_info( &format!( "Volume Envelope Size:\t{}", envelope.len() ) );
                        print_info( &format!( "Loop at:\t{:?}", loop_at ) );

                        PsgEnvelope {
                            envelope,
                            loop_at,
                            settings: HashMap::new()
                        }
                    };

                    // Arpeggio envelope
                    let arpeggio = {
                        let size = get_u8( bytes.by_ref() )?;
                        let mut envelope: Vec<i32> = Vec::new();

                        for _i in 0..size {
                            envelope.push( get_i32( bytes.by_ref() )? - 12 );
                        }

                        let loop_at: Option<usize> = if size > 0 {
                            let loop_position = get_i8( bytes.by_ref() )?;
                            if loop_position != -1 {
                                Some( loop_position.try_into()? )
                            } else {
                                None
                            }
                        } else {
                            None
                        };

                        let settings = HashMap::from([
                            ( "arpeggio_macro_fixed", get_u8( bytes.by_ref() )? == 1 )
                        ]);

                        print_info( &format!( "Arpeggio Envelope Size:\t{}", envelope.len() ) );
                        print_info( &format!( "Arpeggio Macro Fixed:\t{}", settings[ "arpeggio_macro_fixed" ] ) );
                        print_info( &format!( "Loop at:\t{:?}", loop_at ) );

                        PsgEnvelope {
                            envelope,
                            loop_at,
                            settings
                        }
                    };

                    // Noise envelope
                    let noise = {
                        let size = get_u8( bytes.by_ref() )?;
                        let mut envelope: Vec<u32> = Vec::new();

                        for _i in 0..size {
                            envelope.push( get_u32( bytes.by_ref() )? );
                        }

                        let loop_at: Option<usize> = if size > 0 {
                            let loop_position = get_i8( bytes.by_ref() )?;
                            if loop_position != -1 {
                                Some( loop_position.try_into()? )
                            } else {
                                None
                            }
                        } else {
                            None
                        };

                        print_info( &format!( "Noise Envelope Size:\t{}", envelope.len() ) );
                        print_info( &format!( "Loop at:\t{:?}", loop_at ) );

                        PsgEnvelope {
                            envelope,
                            loop_at,
                            settings: HashMap::new()
                        }
                    };

                    // Wavetable envelope
                    let wavetable = {
                        let size = get_u8( bytes.by_ref() )?;
                        let mut envelope: Vec<u32> = Vec::new();

                        for _i in 0..size {
                            envelope.push( get_u32( bytes.by_ref() )? );
                        }

                        let loop_at: Option<usize> = if size > 0 {
                            let loop_position = get_i8( bytes.by_ref() )?;
                            if loop_position != -1 {
                                Some( loop_position.try_into()? )
                            } else {
                                None
                            }
                        } else {
                            None
                        };

                        print_info( &format!( "WavTbl Envelope Size:\t{}\n", envelope.len() ) );
                        print_info( &format!( "Loop at:\t{:?}", loop_at ) );

                        PsgEnvelope {
                            envelope,
                            loop_at,
                            settings: HashMap::new()
                        }
                    };

                    InstrumentType::PsgDcsg( PsgSettings { volume, arpeggio, noise, wavetable } )
                },
                1 => {
                    print_info( "Fm Instrument" );

                    let alg = get_u8( bytes.by_ref() )?;
                    let fb = get_u8( bytes.by_ref() )?;
                    let fms = get_u8( bytes.by_ref() )?;
                    let ams = get_u8( bytes.by_ref() )?;

                    print_info( &format!( "Alg:\t{}", alg ) );
                    print_info( &format!( "Fb:\t{}", fb ) );
                    print_info( &format!( "FMS:\t{}", fms ) );
                    print_info( &format!( "AMS:\t{}", ams ) );

                    let mut operators: [Fm2612Operator; 4] = [
                        Fm2612Operator::default(),
                        Fm2612Operator::default(),
                        Fm2612Operator::default(),
                        Fm2612Operator::default()
                    ];

                    // Operator 1
                    {
                        operators[ 0 ].am = get_u8( bytes.by_ref() )?;
                        operators[ 0 ].ar = get_u8( bytes.by_ref() )?;
                        operators[ 0 ].dr = get_u8( bytes.by_ref() )?;
                        operators[ 0 ].mult = get_u8( bytes.by_ref() )?;
                        operators[ 0 ].rr = get_u8( bytes.by_ref() )?;
                        operators[ 0 ].sl = get_u8( bytes.by_ref() )?;
                        operators[ 0 ].tl = get_u8( bytes.by_ref() )?;
                        operators[ 0 ].dt2 = get_u8( bytes.by_ref() )?;
                        operators[ 0 ].rs = get_u8( bytes.by_ref() )?;
                        operators[ 0 ].dt = get_i8( bytes.by_ref() )? - 3;
                        operators[ 0 ].d2r = get_u8( bytes.by_ref() )?;
                        operators[ 0 ].ssg_mode = get_u8( bytes.by_ref() )?;

                        print_info( &format!( "Op 1 AM:\t{}", operators[ 0 ].am ) );
                        print_info( &format!( "Op 1 AR:\t{}", operators[ 0 ].ar ) );
                        print_info( &format!( "Op 1 DR:\t{}", operators[ 0 ].dr ) );
                        print_info( &format!( "Op 1 MULT:\t{}", operators[ 0 ].mult ) );
                        print_info( &format!( "Op 1 RR:\t{}", operators[ 0 ].rr ) );
                        print_info( &format!( "Op 1 SL:\t{}", operators[ 0 ].sl ) );
                        print_info( &format!( "Op 1 TL:\t{}", operators[ 0 ].tl ) );
                        print_info( &format!( "Op 1 DT2:\t{}", operators[ 0 ].dt2 ) );
                        print_info( &format!( "Op 1 RS:\t{}", operators[ 0 ].rs ) );
                        print_info( &format!( "Op 1 DT:\t{}", operators[ 0 ].dt ) );
                        print_info( &format!( "Op 1 D2R:\t{}", operators[ 0 ].d2r ) );
                        print_info( &format!( "Op 1 SSG_MODE:\t{}\n", operators[ 0 ].ssg_mode ) );
                    }

                    // Operator 3
                    {
                        operators[ 1 ].am = get_u8( bytes.by_ref() )?;
                        operators[ 1 ].ar = get_u8( bytes.by_ref() )?;
                        operators[ 1 ].dr = get_u8( bytes.by_ref() )?;
                        operators[ 1 ].mult = get_u8( bytes.by_ref() )?;
                        operators[ 1 ].rr = get_u8( bytes.by_ref() )?;
                        operators[ 1 ].sl = get_u8( bytes.by_ref() )?;
                        operators[ 1 ].tl = get_u8( bytes.by_ref() )?;
                        operators[ 1 ].dt2 = get_u8( bytes.by_ref() )?;
                        operators[ 1 ].rs = get_u8( bytes.by_ref() )?;
                        operators[ 1 ].dt = get_i8( bytes.by_ref() )? - 3;
                        operators[ 1 ].d2r = get_u8( bytes.by_ref() )?;
                        operators[ 1 ].ssg_mode = get_u8( bytes.by_ref() )?;

                        print_info( &format!( "Op 3 AM:\t{}", operators[ 1 ].am ) );
                        print_info( &format!( "Op 3 AR:\t{}", operators[ 1 ].ar ) );
                        print_info( &format!( "Op 3 DR:\t{}", operators[ 1 ].dr ) );
                        print_info( &format!( "Op 3 MULT:\t{}", operators[ 1 ].mult ) );
                        print_info( &format!( "Op 3 RR:\t{}", operators[ 1 ].rr ) );
                        print_info( &format!( "Op 3 SL:\t{}", operators[ 1 ].sl ) );
                        print_info( &format!( "Op 3 TL:\t{}", operators[ 1 ].tl ) );
                        print_info( &format!( "Op 3 DT2:\t{}", operators[ 1 ].dt2 ) );
                        print_info( &format!( "Op 3 RS:\t{}", operators[ 1 ].rs ) );
                        print_info( &format!( "Op 3 DT:\t{}", operators[ 1 ].dt ) );
                        print_info( &format!( "Op 3 D2R:\t{}", operators[ 1 ].d2r ) );
                        print_info( &format!( "Op 3 SSG_MODE:\t{}\n", operators[ 1 ].ssg_mode ) );
                    }

                    // Operator 2
                    {
                        operators[ 2 ].am = get_u8( bytes.by_ref() )?;
                        operators[ 2 ].ar = get_u8( bytes.by_ref() )?;
                        operators[ 2 ].dr = get_u8( bytes.by_ref() )?;
                        operators[ 2 ].mult = get_u8( bytes.by_ref() )?;
                        operators[ 2 ].rr = get_u8( bytes.by_ref() )?;
                        operators[ 2 ].sl = get_u8( bytes.by_ref() )?;
                        operators[ 2 ].tl = get_u8( bytes.by_ref() )?;
                        operators[ 2 ].dt2 = get_u8( bytes.by_ref() )?;
                        operators[ 2 ].rs = get_u8( bytes.by_ref() )?;
                        operators[ 2 ].dt = get_i8( bytes.by_ref() )? - 3;
                        operators[ 2 ].d2r = get_u8( bytes.by_ref() )?;
                        operators[ 2 ].ssg_mode = get_u8( bytes.by_ref() )?;

                        print_info( &format!( "Op 2 AM:\t{}", operators[ 2 ].am ) );
                        print_info( &format!( "Op 2 AR:\t{}", operators[ 2 ].ar ) );
                        print_info( &format!( "Op 2 DR:\t{}", operators[ 2 ].dr ) );
                        print_info( &format!( "Op 2 MULT:\t{}", operators[ 2 ].mult ) );
                        print_info( &format!( "Op 2 RR:\t{}", operators[ 2 ].rr ) );
                        print_info( &format!( "Op 2 SL:\t{}", operators[ 2 ].sl ) );
                        print_info( &format!( "Op 2 TL:\t{}", operators[ 2 ].tl ) );
                        print_info( &format!( "Op 2 DT2:\t{}", operators[ 2 ].dt2 ) );
                        print_info( &format!( "Op 2 RS:\t{}", operators[ 2 ].rs ) );
                        print_info( &format!( "Op 2 DT:\t{}", operators[ 2 ].dt ) );
                        print_info( &format!( "Op 2 D2R:\t{}", operators[ 2 ].d2r ) );
                        print_info( &format!( "Op 2 SSG_MODE:\t{}\n", operators[ 2 ].ssg_mode ) );
                    }

                    // Operator 4
                    {
                        operators[ 3 ].am = get_u8( bytes.by_ref() )?;
                        operators[ 3 ].ar = get_u8( bytes.by_ref() )?;
                        operators[ 3 ].dr = get_u8( bytes.by_ref() )?;
                        operators[ 3 ].mult = get_u8( bytes.by_ref() )?;
                        operators[ 3 ].rr = get_u8( bytes.by_ref() )?;
                        operators[ 3 ].sl = get_u8( bytes.by_ref() )?;
                        operators[ 3 ].tl = get_u8( bytes.by_ref() )?;
                        operators[ 3 ].dt2 = get_u8( bytes.by_ref() )?;
                        operators[ 3 ].rs = get_u8( bytes.by_ref() )?;
                        operators[ 3 ].dt = get_i8( bytes.by_ref() )? - 3;
                        operators[ 3 ].d2r = get_u8( bytes.by_ref() )?;
                        operators[ 3 ].ssg_mode = get_u8( bytes.by_ref() )?;

                        print_info( &format!( "Op 4 AM:\t{}", operators[ 3 ].am ) );
                        print_info( &format!( "Op 4 AR:\t{}", operators[ 3 ].ar ) );
                        print_info( &format!( "Op 4 DR:\t{}", operators[ 3 ].dr ) );
                        print_info( &format!( "Op 4 MULT:\t{}", operators[ 3 ].mult ) );
                        print_info( &format!( "Op 4 RR:\t{}", operators[ 3 ].rr ) );
                        print_info( &format!( "Op 4 SL:\t{}", operators[ 3 ].sl ) );
                        print_info( &format!( "Op 4 TL:\t{}", operators[ 3 ].tl ) );
                        print_info( &format!( "Op 4 DT2:\t{}", operators[ 3 ].dt2 ) );
                        print_info( &format!( "Op 4 RS:\t{}", operators[ 3 ].rs ) );
                        print_info( &format!( "Op 4 DT:\t{}", operators[ 3 ].dt ) );
                        print_info( &format!( "Op 4 D2R:\t{}", operators[ 3 ].d2r ) );
                        print_info( &format!( "Op 4 SSG_MODE:\t{}\n", operators[ 3 ].ssg_mode ) );
                    }

                    InstrumentType::Fm2612( Fm2612Settings { alg, fb, fms, ams, operators } )
                },
                _ => return Err( format!( "invalid file: invalid instrument mode {}", mode ) )?
            };

            instruments.push( Instrument { name, instrument_type } );
        }

        // Version 27 of DMF specifies "1 wavetable of 0 bytes" for sega megadrive
        // They will never be used in the megadrive, barring use of the audio
        // expansion on the cartridge slot (e.g. mp3 fpga, spc700 fpga)
        // It may just be easier here to eat the wavetables as they are indicated
        // instead of throwing a validation error if anything is present here.
        let wavetable_len = get_u8( bytes.by_ref() )?;
        for _ in 0..wavetable_len {
            let wavetable_size: usize = get_u32( bytes.by_ref() )?.try_into()?;
            if wavetable_size > 0 {
                skip( bytes.by_ref(), wavetable_size * 4 );
            }
        }

        // (channel, patterns)
        let mut loop_at: Option<u32> = None;
        let mut channel_patterns: Vec<Vec<PatternRow>> = Vec::new();
        for channel in 0..system_total_channels {
            let mut channel_rows: Vec<PatternRow> = Vec::new();
            let num_effects = get_u8( bytes.by_ref() )?;
            print_info( &format!( "Patterns for Ch. {}", channel ) );

            for _ in 0..patterns_count {
                for row_id in 0..rows_per_pattern {
                    let note = get_u16( bytes.by_ref() )?;
                    let octave = get_u16( bytes.by_ref() )? as u8;

                    let note: Option<Note> = if note == 0 && octave == 0 {
                        None
                    } else {
                        Some(
                            match note {
                                100 => Note::NoteOff,
                                // check: do these octave shifts make sense for all dmf modules?
                                // they were needed on the test dmf but no documentation supports it
                                // test dmf sounds off key otherwise...
                                1 => Note::CSharp( if (6..=9).contains( &channel ) { octave - 1 } else { octave } ),
                                2 => Note::D( if (6..=9).contains( &channel ) { octave - 1 } else { octave } ),
                                3 => Note::DSharp( if (6..=9).contains( &channel ) { octave - 1 } else { octave } ),
                                4 => Note::E( if (6..=9).contains( &channel ) { octave - 1 } else { octave } ),
                                5 => Note::F( if (6..=9).contains( &channel ) { octave - 1 } else { octave } ),
                                6 => Note::FSharp( if (6..=9).contains( &channel ) { octave - 1 } else { octave } ),
                                7 => Note::G( if (6..=9).contains( &channel ) { octave - 1 } else { octave } ),
                                8 => Note::GSharp( if (6..=9).contains( &channel ) { octave - 1 } else { octave } ),
                                9 => Note::A( if (6..=9).contains( &channel ) { octave - 1 } else { octave } ),
                                10 => Note::ASharp( if (6..=9).contains( &channel ) { octave - 1 } else { octave } ),
                                11 => Note::B( if (6..=9).contains( &channel ) { octave - 1 } else { octave } ),
                                12 => Note::C( if (6..=9).contains( &channel ) { octave } else { octave + 1 } ), // deflemask y u shift the octave down 1 for C??
                                0 => Note::C( if (6..=9).contains( &channel ) { octave - 1 } else { octave } ), // deflemask y u do this?? 0 is not documented
                                _ => return Err( format!( "invalid file: invalid note id {}", note ) )?
                            }
                        )
                    };

                    let volume = get_i16( bytes.by_ref() )?;
                    let volume: Option<u8> = if volume == -1 {
                        None
                    } else {
                        Some( volume as u8 )
                    };

                    let mut effects: LinkedHashSet<Effect> = LinkedHashSet::new();
                    for _ in 0..num_effects {
                        let effect_code = get_i16( bytes.by_ref() )?;
                        let effect_value = get_i16( bytes.by_ref() )?;
                        let effect_value: Option<u8> = if effect_value == -1 { None } else { Some( effect_value as u8 ) };

                        if effect_code != -1 {
                            effects.insert(
                                match effect_code as u8 {
                                    DMF_EFFECT_ARPEGGIO => Effect::Arpeggio {
                                        first_shift: effect_value.ok_or( "invalid file: expected effect value for arpeggio" )? >> 4,
                                        second_shift: effect_value.ok_or( "invalid file: expected effect value for arpeggio" )? & 0x0F
                                    },
                                    DMF_EFFECT_PORTAMENTO_UP => Effect::PortamentoUp {
                                        speed: effect_value.ok_or( "invalid file: expected effect value for portamento up" )?
                                    },
                                    DMF_EFFECT_PORTAMENTO_DOWN => Effect::PortamentoDown {
                                        speed: effect_value.ok_or( "invalid file: expected effect value for portamento down`" )?
                                    },
                                    DMF_EFFECT_PORTA_TO_NOTE => Effect::PortamentoToNote {
                                        speed: effect_value.ok_or( "invalid file: expected effect value for porta to note" )?
                                    },
                                    DMF_EFFECT_SET_PANNING => {
                                        let effect_value = effect_value.ok_or( "invalid file: expected effect value for panning" )?;

                                        Effect::SetPanning {
                                            left: effect_value == 0x10 || effect_value == 0x11,
                                            right: effect_value == 0x01 || effect_value == 0x11
                                        }
                                    },
                                    DMF_EFFECT_POSITION_JUMP => {
                                        let pattern = effect_value.ok_or( "invalid file: expected effect value for position jump" )?;

                                        if let None = loop_at {
                                            loop_at = Some( pattern as u32 * rows_per_pattern );
                                        }

                                        Effect::PositionJump { pattern }
                                    },
                                    DMF_EFFECT_PATTERN_BREAK => {
                                        let start_row = effect_value.ok_or( "invalid file: expected effect value for start row" )?;

                                        Effect::JumpToNextPattern { start_row }
                                    },
                                    DMF_EFFECT_DAC_ENABLE => {
                                        let enabled = effect_value.ok_or( "invalid file: expected effect value for dac enable" )? != 0;

                                        Effect::DacEnable { enabled }
                                    },
                                    DMF_EFFECT_PSG_NOISE_MODE => {
                                        let effect_value = effect_value.ok_or( "invalid file: expected effect value for psg noise mode" )?;
                                        let mode = match effect_value {
                                            0x00 | 0x01 => DcsgChannelMode::FixedFrequency,
                                            0x10 | 0x11 => DcsgChannelMode::Ch3Frequency,
                                            _ => return Err( "invalid file: invalid effect value setting for psg noise mode" )?
                                        };
                                        let noise_type = match effect_value {
                                            0x00 | 0x10 => NoiseType::PeriodicNoise,
                                            0x01 | 0x11 => NoiseType::WhiteNoise,
                                            _ => return Err( "invalid file: invalid effect value setting for psg noise mode" )?
                                        };

                                        Effect::DcsgNoiseMode { mode, noise_type }
                                    },
                                    DMF_EFFECT_NOTE_CUT => {
                                        let after_ticks = effect_value.ok_or( "invalid file: expected effect value setting for note cut" )?;

                                        Effect::NoteCut { after_ticks }
                                    }
                                    _ => Effect::UnknownEffect{ code: effect_code as u8, parameter: effect_value }
                                }
                            );
                        }
                    }

                    let instrument_index = get_i16( bytes.by_ref() )?;
                    let instrument_index: Option<u16> = if instrument_index != -1 {
                        Some( instrument_index.try_into()? )
                    } else {
                        None
                    };

                    let pattern_row = PatternRow { note, volume, effects, instrument_index };
                    print_info( &format!( "{}:\t{:?}", row_id, pattern_row ) );

                    channel_rows.push( pattern_row );
                }
            }

            channel_patterns.push( channel_rows );
        }

        // If at least one PSG channel never defines an instrument, insert the default PSG instrument
        // which is a repeat of zero attentuation.
        print_info( "Default PSG instruments:" );
        if DmfModule::uses_default_psg( &channel_patterns )? {
            print_info( "One or more channels use the default PSG instrument" );
            instruments.push( Instrument{
                name: format!( "__reskit_default_psg_instrument" ),
                instrument_type: InstrumentType::PsgDcsg( PsgSettings {
                    volume: PsgEnvelope {
                        envelope: vec![ 0x0F as u32 ],
                        loop_at: Some( 0 ),
                        settings: HashMap::new()
                    },
                    arpeggio: PsgEnvelope {
                        envelope: vec![],
                        loop_at: None,
                        settings: HashMap::new()
                    },
                    noise: PsgEnvelope {
                        envelope: vec![],
                        loop_at: None,
                        settings: HashMap::new()
                    },
                    wavetable: PsgEnvelope {
                        envelope: vec![],
                        loop_at: None,
                        settings: HashMap::new()
                    }
                } )
            } )
        } else {
            print_info( "All PSG channels set instruments" );
        }

        print_info( "PCM Samples:" );
        let mut samples: Vec<Sample> = Vec::new();
        let num_samples = get_u8( bytes.by_ref() )?;
        for i in 0..num_samples {
            let sample_size = get_u32( bytes.by_ref() )?;
            let sample_name_chars = get_u8( bytes.by_ref() )?;
            let name = sanitize_string( get_string( bytes.by_ref(), sample_name_chars.into() )? );
            let rate = match get_u8( bytes.by_ref() )? {
                1 => 8000,
                2 => 11025,
                3 => 16000,
                4 => 22050,
                5 => 32000,
                invalid => return Err( format!( "invalid file: invalid sample rate {}", invalid ) )?
            };

            let pitch = get_i8( bytes.by_ref() )? - 5;
            let amp = get_u8( bytes.by_ref() )?;
            let bitrate = match get_u8( bytes.by_ref() )? {
                8 => SampleFormat::Bits8,
                16 => SampleFormat::Bits16,
                invalid => return Err( format!( "invalid file: invalid bitrate {}", invalid ) )?
            };


            // Fast forward 8 bytes in the DMF stream
            // (I have no idea what these fields are used for)
            skip( bytes.by_ref(), 8 );

            let mut sample = Sample { name, rate, pitch, amp, bitrate, data: Vec::new() };

            print_info( &format!( "{}:\t{:?}", i, sample ) );

            for _ in 0..sample_size {
                sample.data.push( get_i16( bytes.by_ref() )? );
            }

            samples.push( sample );
        }

        Ok(
            DmfModule {
                path: path.to_string(), platform, version, time_base, speed_a, speed_b, frame_mode, rows_per_pattern,
                pattern_matrix, loop_at, channel_patterns, instruments, samples
            }
         )
    }

    pub fn uses_default_psg( channel_patterns: &Vec<Vec<PatternRow>> ) -> Result<bool, Box<dyn Error>> {
        let mut channels: [Channel; 4] = [
            Channel::new( 1 ),
            Channel::new( 2 ),
            Channel::new( 3 ),
            Channel::new( 4 )
        ];

        for channel_id in 6..=9 {
            let channel_patterns = &channel_patterns[ channel_id ];
            for row in channel_patterns {
                // If a set instrument is encountered, just set the instrument on the channel
                // We will check all three channels to see if at least one does not contain
                // an active instrument.
                if row.instrument_index.is_some() {
                    channels[ channel_id - 6 ].active_instrument = Some( 0 );
                }
            }
        }

        for channel in channels {
            if channel.active_instrument.is_none() {
                return Ok( true )
            }
        }

        Ok( false )
    }

}