import {Pitch} from "../../../Common/DataObjects/Pitch";
import {AbstractNotationInstruction} from "./AbstractNotationInstruction";
import {NoteEnum} from "../../../Common/DataObjects/Pitch";
import {AccidentalEnum} from "../../../Common/DataObjects/Pitch";
import {ArgumentOutOfRangeException} from "../../Exceptions";

/**
 * A [[ClefInstruction]] is the clef placed at the beginning of the stave, which indicates the pitch of the notes.
 */
export class ClefInstruction extends AbstractNotationInstruction {
    constructor(clefType: ClefEnum = ClefEnum.G, octaveOffset: number = 0, line: number = 2) {
        super(undefined); // FIXME? Missing SourceStaffEntry!
        this.line = line;
        this.clefType = clefType;
        this.octaveOffset = octaveOffset;
        this.calcParameters();
    }

    private clefType: ClefEnum = ClefEnum.G;
    private line: number = 2;
    private octaveOffset: number = 0;
    private clefPitch: Pitch;
    private referenceCyPosition: number;

    public static getDefaultClefFromMidiInstrument(instrument: MidiInstrument): ClefInstruction {
        switch (instrument) {
            case MidiInstrument.Acoustic_Grand_Piano:
                return new ClefInstruction(ClefEnum.F, 0, 4);
            case MidiInstrument.Electric_Bass_finger:
                return new ClefInstruction(ClefEnum.F, 0, 4);
            case MidiInstrument.Electric_Bass_pick:
                return new ClefInstruction(ClefEnum.F, 0, 4);
            case MidiInstrument.Fretless_Bass:
                return new ClefInstruction(ClefEnum.F, 0, 4);
            case MidiInstrument.Slap_Bass_1:
                return new ClefInstruction(ClefEnum.F, 0, 4);
            case MidiInstrument.Slap_Bass_2:
                return new ClefInstruction(ClefEnum.F, 0, 4);
            case MidiInstrument.Synth_Bass_1:
                return new ClefInstruction(ClefEnum.F, 0, 4);
            case MidiInstrument.Synth_Bass_2:
                return new ClefInstruction(ClefEnum.F, 0, 4);
            case MidiInstrument.Contrabass:
                return new ClefInstruction(ClefEnum.F, 0, 4);
            default:
                return new ClefInstruction(ClefEnum.G, 0, 2);
        }
    }

    public static getAllPossibleClefs(): ClefInstruction[] {
        const clefList: ClefInstruction[] = [];
        for (let i: number = 0; i <= 2; i++) {
            const clefInstructionG: ClefInstruction = new ClefInstruction(ClefEnum.G, i, 2);
            clefList.push(clefInstructionG);
        }
        for (let j: number = -2; j <= 0; j++) {
            const clefInstructionF: ClefInstruction = new ClefInstruction(ClefEnum.F, j, 4);
            clefList.push(clefInstructionF);
        }
        return clefList;
    }

    public static isSupportedClef(clef: ClefEnum): boolean {
        switch (clef) {
            case ClefEnum.G:
            case ClefEnum.F:
            case ClefEnum.C:
            case ClefEnum.percussion:
            case ClefEnum.TAB:
                return true;
            default:
                return false;
        }
    }

    public get ClefType(): ClefEnum {
        return this.clefType;
    }

    public set ClefType(value: ClefEnum) {
        this.clefType = value;
    }

    public get Line(): number {
        return this.line;
    }

    public set Line(value: number) {
        this.line = value;
    }

    public get OctaveOffset(): number {
        return this.octaveOffset;
    }

    public set OctaveOffset(value: number) {
        this.octaveOffset = value;
    }

    public get ClefPitch(): Pitch {
        return this.clefPitch;
    }

    public set ClefPitch(value: Pitch) {
        this.clefPitch = value;
    }

    public get ReferenceCyPosition(): number {
        return this.referenceCyPosition;
    }

    public set ReferenceCyPosition(value: number) {
        this.referenceCyPosition = value;
    }

    public Equals(other: ClefInstruction): boolean {
        if (this === other) {
            return true;
        }
        if (!this || !other) {
            return false;
        }
        return (this.clefPitch === other.clefPitch && this.Line === other.Line);
    }

    public NotEqual(clef2: ClefInstruction): boolean {
        return !this.Equals(clef2);
    }

    public ToString(): string {
        return "ClefType: " + this.clefType;
    }

    private calcParameters(): void {
        switch (this.clefType) {
            case ClefEnum.G:
                this.clefPitch = new Pitch(NoteEnum.G, 1 + this.octaveOffset, AccidentalEnum.NONE);
                this.referenceCyPosition = (5 - this.line) + 2;
                break;
            case ClefEnum.F:
                this.clefPitch = new Pitch(NoteEnum.F, 0 + this.octaveOffset, AccidentalEnum.NONE);
                this.referenceCyPosition = (5 - this.line) + 1.5;
                break;
            case ClefEnum.C:
                this.clefPitch = new Pitch(NoteEnum.C, 1 + this.octaveOffset, AccidentalEnum.NONE);
                this.referenceCyPosition = (5 - this.line);
                break;
            case ClefEnum.percussion:
                this.clefPitch = new Pitch(NoteEnum.C, 2, AccidentalEnum.NONE);
                this.referenceCyPosition = 2;
                break;
            case ClefEnum.TAB:
                this.clefPitch = new Pitch(NoteEnum.G, 0, AccidentalEnum.NONE);
                this.referenceCyPosition = 0;
                break;
            default:
                throw new ArgumentOutOfRangeException("clefType");
        }
    }
}

export enum ClefEnum {
    G = 0,
    F = 1,
    C = 2,
    percussion = 3,
    TAB = 4
}


export enum MidiInstrument {
    None = -1,
    Acoustic_Grand_Piano,
    Bright_Acoustic_Piano,
    Electric_Grand_Piano,
    Honky_tonk_Piano,
    Electric_Piano_1,
    Electric_Piano_2,
    Harpsichord,
    Clavinet,
    Celesta,
    Glockenspiel,
    Music_Box,
    Vibraphone,
    Marimba,
    Xylophone,
    Tubular_Bells,
    Dulcimer,
    Drawbar_Organ,
    Percussive_Organ,
    Rock_Organ,
    Church_Organ,
    Reed_Organ,
    Accordion,
    Harmonica,
    Tango_Accordion,
    Acoustic_Guitar_nylon,
    Acoustic_Guitar_steel,
    Electric_Guitar_jazz,
    Electric_Guitar_clean,
    Electric_Guitar_muted,
    Overdriven_Guitar,
    Distortion_Guitar,
    Guitar_harmonics,
    Acoustic_Bass,
    Electric_Bass_finger,
    Electric_Bass_pick,
    Fretless_Bass,
    Slap_Bass_1,
    Slap_Bass_2,
    Synth_Bass_1,
    Synth_Bass_2,
    Violin,
    Viola,
    Cello,
    Contrabass,
    Tremolo_Strings,
    Pizzicato_Strings,
    Orchestral_Harp,
    Timpani,
    String_Ensemble_1,
    String_Ensemble_2,
    Synth_Strings_1,
    Synth_Strings_2,
    Choir_Aahs,
    Voice_Oohs,
    Synth_Voice,
    Orchestra_Hit,
    Trumpet,
    Trombone,
    Tuba,
    Muted_Trumpet,
    French_Horn,
    Brass_Section,
    Synth_Brass_1,
    Synth_Brass_2,
    Soprano_Sax,
    Alto_Sax,
    Tenor_Sax,
    Baritone_Sax,
    Oboe,
    English_Horn,
    Bassoon,
    Clarinet,
    Piccolo,
    Flute,
    Recorder,
    Pan_Flute,
    Blown_Bottle,
    Shakuhachi,
    Whistle,
    Ocarina,
    Lead_1_square,
    Lead_2_sawtooth,
    Lead_3_calliope,
    Lead_4_chiff,
    Lead_5_charang,
    Lead_6_voice,
    Lead_7_fifths,
    Lead_8_bass_lead,
    Pad_1_new_age,
    Pad_2_warm,
    Pad_3_polysynth,
    Pad_4_choir,
    Pad_5_bowed,
    Pad_6_metallic,
    Pad_7_halo,
    Pad_8_sweep,
    FX_1_rain,
    FX_2_soundtrack,
    FX_3_crystal,
    FX_4_atmosphere,
    FX_5_brightness,
    FX_6_goblins,
    FX_7_echoes,
    FX_8_scifi,
    Sitar,
    Banjo,
    Shamisen,
    Koto,
    Kalimba,
    Bag_pipe,
    Fiddle,
    Shanai,
    Tinkle_Bell,
    Agogo,
    Steel_Drums,
    Woodblock,
    Taiko_Drum,
    Melodic_Tom,
    Synth_Drum,
    Reverse_Cymbal,
    Guitar_Fret_Noise,
    Breath_Noise,
    Seashore,
    Bird_Tweet,
    Telephone_Ring,
    Helicopter,
    Applause,
    Gunshot,
    euphonium,
    AltoClarinetInEb = 200,
    TrumpetInBb = 201,
    BaritoneSaxophone = 202,
    TenorSaxophone = 203,
    ClarinetInEb = 204,
    Euphonium,
    Alto_Clarinet,
    Bass_Clarinet,
    Bass_Trombone,
    clarinet_in_bb,
    clarinet_in_eb,
    Percussion = 135
}
// 以上数字=为自定以200开始排序，请同时在播放器中同时配置
