use std::{collections::HashMap, error::Error};
use linked_hash_set::LinkedHashSet;

#[derive(Clone, Debug, PartialEq)]
pub struct PsgEnvelope< DataFormat: PartialEq + Clone > {
    pub envelope: Vec<DataFormat>,
    pub loop_at: Option<usize>,
    pub settings: HashMap<&'static str, bool>
}

#[derive(Clone, Debug, PartialEq)]
pub struct PsgSettings {
    pub volume: PsgEnvelope< u32 >,
    pub arpeggio: PsgEnvelope< i32 >,
    pub noise: PsgEnvelope< u32 >,
    pub wavetable: PsgEnvelope< u32 >
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct Fm2612Operator {
    pub am: u8,
    pub ar: u8,
    pub dr: u8,
    pub mult: u8,
    pub rr: u8,
    pub sl: u8,
    pub tl: u8,
    pub dt2: u8,
    pub rs: u8,
    pub dt: i8,
    pub d2r: u8,
    pub ssg_mode: u8
}

#[derive(Clone, Debug, PartialEq)]
pub struct Fm2612Settings {
    pub alg: u8,
    pub fb: u8,
    pub fms: u8,
    pub ams: u8,
    pub operators: [Fm2612Operator; 4]
}

#[derive(Clone, Debug, PartialEq)]
pub enum InstrumentType {
    Fm2612( Fm2612Settings ),
    PsgDcsg( PsgSettings )
}

#[derive(Clone, PartialEq, Debug)]
pub struct Instrument {
    pub name: String,
    pub instrument_type: InstrumentType
}

pub struct CombinedAssets {
    pub output_prefix: String,
    pub sequences: Vec<String>,
    pub instruments: Vec<Instrument>,
    pub samples: Vec<Sample>
}

#[derive(Clone, PartialEq, Debug)]
pub enum SampleFormat {
    Bits8,
    Bits16
}

#[derive(Clone, PartialEq, Debug)]
pub struct Sample {
    pub name: String,
    pub rate: u32,
    pub pitch: i8,
    pub amp: u8,
    pub bitrate: SampleFormat,
    pub data: Vec<i16>
}

#[derive(Clone, Copy, Debug)]
pub enum Note {
    NoteOff,
    CSharp( u8 ),
    D( u8 ),
    DSharp( u8 ),
    E( u8 ),
    F( u8 ),
    FSharp( u8 ),
    G( u8 ),
    GSharp( u8 ),
    A( u8 ),
    ASharp( u8 ),
    B( u8 ),
    C( u8 )
}

#[derive(Clone, Copy, Debug)]
pub struct OctaveFrequency {
    pub octave: u8,
    pub frequency: u16
}

#[derive(Default, Debug)]
pub struct Channel {
    pub id: u8,
    pub active_instrument: Option<u16>,
    pub active_note: Option<OctaveFrequency>,
    pub note_keyed: bool,
    pub current_volume: Option<u8>,
    pub active_effects: LinkedHashSet<Effect>
}

#[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)]
pub enum DcsgChannelMode {
    Ch3Frequency,
    FixedFrequency
}

#[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)]
pub enum NoiseType {
    PeriodicNoise,
    WhiteNoise
}

#[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)]
pub enum Effect {
    Arpeggio { first_shift: u8, second_shift: u8 },
    PortamentoUp { speed: u8 },
    PortamentoDown { speed: u8 },
    PortamentoToNote { speed: u8 },
    SetPanning { left: bool, right: bool },
    PositionJump { pattern: u8 },
    JumpToNextPattern { start_row: u8 },
    DacEnable { enabled: bool },
    DcsgNoiseMode { mode: DcsgChannelMode, noise_type: NoiseType },
    NoteCut { after_ticks: u8 },
    UnknownEffect { code: u8, parameter: Option<u8> }
}

#[derive(Debug)]
pub struct PatternRow {
    pub note: Option<Note>,
    pub volume: Option<u8>,
    pub effects: LinkedHashSet<Effect>,
    pub instrument_index: Option<u16>
}

impl Channel {

    pub fn new( id: u8 ) -> Channel {
        Channel {
            id,
            active_instrument: None,
            active_note: None,
            note_keyed: false,
            current_volume: None,
            active_effects: LinkedHashSet::new()
        }
    }

}

impl Note {

    pub fn get_octave( &self ) -> Result<u8, Box<dyn Error>> {
        Ok( match self {
            Note::NoteOff => return Err( "internal error: attempted to get octave for Note::NoteOff" )?,
            Note::CSharp( octave ) => *octave,
            Note::D( octave ) => *octave,
            Note::DSharp( octave ) => *octave,
            Note::E( octave ) => *octave,
            Note::F( octave ) => *octave,
            Note::FSharp( octave ) => *octave,
            Note::G( octave ) => *octave,
            Note::GSharp( octave ) => *octave,
            Note::A( octave ) => *octave,
            Note::ASharp( octave ) => *octave,
            Note::B( octave ) => *octave,
            Note::C( octave ) => *octave
        } )
    }

    pub fn _get_freq( &self ) -> Option<f32> {
        match self {
            Note::NoteOff => None,
            Note::CSharp( octave ) => match octave {
                0 => Some( 17.32 ), 1 => Some( 34.65 ), 2 => Some( 69.30 ), 3 => Some( 138.59 ),
                4 => Some( 277.18 ), 5 => Some( 554.37 ), 6 => Some( 1108.73 ), 7 => Some( 2217.46 ),
                _ => None
            },
            Note::D( octave ) => match octave {
                0 => Some( 18.35 ), 1 => Some( 36.71 ), 2 => Some( 73.42 ), 3 => Some( 146.83 ),
                4 => Some( 293.66 ), 5 => Some( 587.33 ), 6 => Some( 1174.66 ), 7 => Some( 2349.32 ),
                _ => None
            },
            Note::DSharp( octave ) => match octave {
                0 => Some( 19.45 ), 1 => Some( 38.89 ), 2 => Some( 77.78 ), 3 => Some( 155.56 ),
                4 => Some( 311.13 ), 5 => Some( 622.25 ), 6 => Some( 1244.51 ), 7 => Some( 2489.02 ),
                _ => None
            },
            Note::E( octave ) => match octave {
                0 => Some( 20.60 ), 1 => Some( 41.20 ), 2 => Some( 82.41 ), 3 => Some( 164.81 ),
                4 => Some( 329.63 ), 5 => Some( 659.25 ), 6 => Some( 1318.51 ), 7 => Some( 2637.02 ),
                _ => None
            },
            Note::F( octave ) => match octave {
                0 => Some( 21.83 ), 1 => Some( 43.65 ), 2 => Some( 87.31 ), 3 => Some( 174.61 ),
                4 => Some( 349.23 ), 5 => Some( 698.46 ), 6 => Some( 1396.91 ), 7 => Some( 2793.83 ),
                _ => None
            },
            Note::FSharp( octave ) => match octave {
                0 => Some( 23.12 ), 1 => Some( 46.25 ), 2 => Some( 92.50 ), 3 => Some( 185.00 ),
                4 => Some( 369.99 ), 5 => Some( 739.99 ), 6 => Some( 1479.98 ), 7 => Some( 2959.96 ),
                _ => None
            },
            Note::G( octave ) => match octave {
                0 => Some( 24.50 ), 1 => Some( 49.00 ), 2 => Some( 98.00 ), 3 => Some( 196.00 ),
                4 => Some( 392.00 ), 5 => Some( 783.99 ), 6 => Some( 1567.98 ), 7 => Some( 3135.96 ),
                _ => None
            },
            Note::GSharp( octave ) => match octave {
                0 => Some( 25.96 ), 1 => Some( 51.91 ), 2 => Some( 103.83 ), 3 => Some( 207.65 ),
                4 => Some( 415.30 ), 5 => Some( 830.61 ), 6 => Some( 1661.22 ), 7 => Some( 3322.44 ),
                _ => None
            },
            Note::A( octave ) => match octave {
                0 => Some( 27.50 ), 1 => Some( 55.00 ), 2 => Some( 110.00 ), 3 => Some( 220.00 ),
                4 => Some( 440.00 ), 5 => Some( 880.00 ), 6 => Some( 1760.00 ), 7 => Some( 3520.00 ),
                _ => None
            },
            Note::ASharp( octave ) => match octave {
                0 => Some( 29.14 ), 1 => Some( 58.27 ), 2 => Some( 116.54 ), 3 => Some( 233.08 ),
                4 => Some( 466.16 ), 5 => Some( 932.33 ), 6 => Some( 1864.66 ), 7 => Some( 3729.31 ),
                _ => None
            },
            Note::B( octave ) => match octave {
                0 => Some( 30.87 ), 1 => Some( 61.74 ), 2 => Some( 123.47 ), 3 => Some( 246.94 ),
                4 => Some( 493.88 ), 5 => Some( 987.77 ), 6 => Some( 1975.53 ), 7 => Some( 3951.07 ),
                _ => None
            },
            Note::C( octave ) => match octave {
                0 => Some( 16.35 ), 1 => Some( 32.70 ), 2 => Some( 65.41 ), 3 => Some( 130.81 ),
                4 => Some( 261.63 ), 5 => Some( 523.25 ), 6 => Some( 1046.50 ), 7 => Some( 2093.00 ),
                _ => None
            }
        }
    }

}