package com.jinmingyunle.midiplaylib;

import android.app.Application;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;

import com.bassmedia.BassMusicPlay;
import com.bassmedia.PlayerListener;
import com.jinmingyunle.midiplaylib.file.FileUri;
import com.jinmingyunle.midiplaylib.midifile.MidiEvent;
import com.jinmingyunle.midiplaylib.midifile.MidiFile;
import com.jinmingyunle.midiplaylib.midifile.MidiFileException;
import com.jinmingyunle.midiplaylib.midifile.MidiFileReader;
import com.jinmingyunle.midiplaylib.midifile.MidiTrack;

import java.util.ArrayList;

import static com.jinmingyunle.midiplaylib.midifile.MidiFile.EventChannelPressure;
import static com.jinmingyunle.midiplaylib.midifile.MidiFile.EventControlChange;
import static com.jinmingyunle.midiplaylib.midifile.MidiFile.EventKeyPressure;
import static com.jinmingyunle.midiplaylib.midifile.MidiFile.EventNoteOff;
import static com.jinmingyunle.midiplaylib.midifile.MidiFile.EventNoteOn;
import static com.jinmingyunle.midiplaylib.midifile.MidiFile.EventPitchBend;
import static com.jinmingyunle.midiplaylib.midifile.MidiFile.EventProgramChange;
import static com.jinmingyunle.midiplaylib.midifile.MidiFile.MetaEvent;
import static com.jinmingyunle.midiplaylib.midifile.MidiFile.MetaEventEndOfTrack;
import static com.jinmingyunle.midiplaylib.midifile.MidiFile.MetaEventTempo;
import static com.jinmingyunle.midiplaylib.midifile.MidiFile.MetaEventTimeSignature;
import static com.jinmingyunle.midiplaylib.midifile.MidiFile.SysexEvent1;
import static com.jinmingyunle.midiplaylib.midifile.MidiFile.SysexEvent2;

/**
 * midi 播放
 */
public class MidiPlayerUtils {
    private static final String TAG = "MidiPlayerUtilsTAG";
    private Context mContext;
    private static volatile MidiPlayerUtils instance = null;

    public String tempSoundFile;

    private long currentStartPosition;
    public int midiTotalTime = 0;

    public void init(Context context, String filePath) {
        mContext = context;
        tempSoundFile = filePath;
        hasNoteTrackNum = analyseFile(tempSoundFile);
        BassMusicPlay.getInstance().Load(mContext, tempSoundFile);
        Log.i(TAG, "MidiPlayerUtils: 初始化");

    }

    private ArrayList<MidiTrack> tracks = new ArrayList<>();
    private ArrayList<Boolean> trackSelected = new ArrayList<>();
    int hasNoteTrackNum = 0;


    public ArrayList<MidiTrack> getMidiTracks() {
        return tracks;
    }

    public ArrayList<Boolean> getTrackSelected() {
        return trackSelected;
    }

    public void setTrackSelected(ArrayList<Boolean> trackSelected) {
        this.trackSelected = trackSelected;
    }

    public int getHasNoteTrackNum() {
        return hasNoteTrackNum;
    }

    private int analyseFile(String midiFile) {
        FileUri fileUri = new FileUri(Uri.parse(midiFile), midiFile);
        byte[] fileData = fileUri.getData(mContext);
        if (fileData == null || fileData.length <= 6 || !MidiFile.hasMidiHeader(fileData)) {
            return 0;
        }
        Uri uri = fileUri.getUri();
        String title = fileUri.toString();
        FileUri fileuri = new FileUri(uri, title);
        byte[] data = fileuri.getData(mContext);
        String id;
        int len;

        tracks.clear();
        trackSelected.clear();
        boolean trackPerChannel = false;

        MidiFileReader file = new MidiFileReader(data);
        id = file.ReadAscii(4);
        if (!id.equals("MThd")) {
            throw new MidiFileException("Doesn't start with MThd", 0);
        }
        len = file.ReadInt();
        if (len != 6) {
            throw new MidiFileException("Bad MThd header", 4);
        }
        short trackmode = (short) file.ReadShort();
        int num_tracks = file.ReadShort();
        int quarternote = file.ReadShort();

        int hasNoteTrackNum = 0;
        ArrayList<ArrayList<MidiEvent>> allevents = new ArrayList<ArrayList<MidiEvent>>();
        for (int tracknum = 0; tracknum < num_tracks; tracknum++) {
            allevents.add(ReadTrack(file));
            MidiTrack track = new MidiTrack(allevents.get(tracknum), tracknum);
            tracks.add(track);
            trackSelected.add(true);
            if (track.getNotes().size() > 0) {
                hasNoteTrackNum++;
            }
        }
        for (int i = 0; i < tracks.size(); i++) {
            midiTotalTime = midiTotalTime > tracks.get(i).getTime() ? midiTotalTime : tracks.get(i).getTime();
        }
        return hasNoteTrackNum;
    }

    private static ArrayList<MidiEvent> ReadTrack(MidiFileReader file) {
        ArrayList<MidiEvent> result = new ArrayList<MidiEvent>(20);
        int starttime = 0;
        String id = file.ReadAscii(4);

        if (!id.equals("MTrk")) {
            throw new MidiFileException("Bad MTrk header", file.GetOffset() - 4);
        }
        int tracklen = file.ReadInt();
        int trackend = tracklen + file.GetOffset();

        byte eventflag = 0;

        while (file.GetOffset() < trackend) {

            // If the midi file is truncated here, we can still recover.
            // Just return what we've parsed so far.

            int startoffset, deltatime;
            byte peekevent;
            try {
                startoffset = file.GetOffset();
                deltatime = file.ReadVarlen();
                starttime += deltatime;
                peekevent = file.Peek();
            } catch (MidiFileException e) {
                return result;
            }

            MidiEvent mevent = new MidiEvent();
            result.add(mevent);
            mevent.DeltaTime = deltatime;
            mevent.StartTime = starttime;

            // if (peekevent >= EventNoteOff) {
            if (peekevent < 0) {
                mevent.HasEventflag = true;
                eventflag = file.ReadByte();
            }

            //Log.e("debug",  "offset " + startoffset +
            //                " event " + eventflag + " " + EventName(eventflag) +
            //                " start " + starttime + " delta " + mevent.DeltaTime);

            if (eventflag >= EventNoteOn && eventflag < EventNoteOn + 16) {
                mevent.EventFlag = EventNoteOn;
                mevent.Channel = ((byte) (eventflag - EventNoteOn));
                mevent.Notenumber = file.ReadByte();
                mevent.Velocity = file.ReadByte();
            } else if (eventflag >= EventNoteOff && eventflag < EventNoteOff + 16) {
                mevent.EventFlag = EventNoteOff;
                mevent.Channel = ((byte) (eventflag - EventNoteOff));
                mevent.Notenumber = file.ReadByte();
                mevent.Velocity = file.ReadByte();
            } else if (eventflag >= EventKeyPressure &&
                    eventflag < EventKeyPressure + 16) {
                mevent.EventFlag = EventKeyPressure;
                mevent.Channel = ((byte) (eventflag - EventKeyPressure));
                mevent.Notenumber = file.ReadByte();
                mevent.KeyPressure = file.ReadByte();
            } else if (eventflag >= EventControlChange &&
                    eventflag < EventControlChange + 16) {
                mevent.EventFlag = EventControlChange;
                mevent.Channel = ((byte) (eventflag - EventControlChange));
                mevent.ControlNum = file.ReadByte();
                mevent.ControlValue = file.ReadByte();
            } else if (eventflag >= EventProgramChange &&
                    eventflag < EventProgramChange + 16) {
                mevent.EventFlag = EventProgramChange;
                mevent.Channel = ((byte) (eventflag - EventProgramChange));
                mevent.Instrument = file.ReadByte();
            } else if (eventflag >= EventChannelPressure &&
                    eventflag < EventChannelPressure + 16) {
                mevent.EventFlag = EventChannelPressure;
                mevent.Channel = ((byte) (eventflag - EventChannelPressure));
                mevent.ChanPressure = file.ReadByte();
            } else if (eventflag >= EventPitchBend &&
                    eventflag < EventPitchBend + 16) {
                mevent.EventFlag = EventPitchBend;
                mevent.Channel = ((byte) (eventflag - EventPitchBend));
                mevent.PitchBend = (short) file.ReadShort();
            } else if (eventflag == SysexEvent1) {
                mevent.EventFlag = SysexEvent1;
                mevent.Metalength = file.ReadVarlen();
                mevent.Value = file.ReadBytes(mevent.Metalength);
            } else if (eventflag == SysexEvent2) {
                mevent.EventFlag = SysexEvent2;
                mevent.Metalength = file.ReadVarlen();
                mevent.Value = file.ReadBytes(mevent.Metalength);
            } else if (eventflag == MetaEvent) {
                mevent.EventFlag = MetaEvent;
                mevent.Metaevent = file.ReadByte();
                mevent.Metalength = file.ReadVarlen();
                mevent.Value = file.ReadBytes(mevent.Metalength);
                if (mevent.Metaevent == MetaEventTimeSignature) {
                    if (mevent.Metalength < 2) {
                        throw new MidiFileException(
                                "Meta Event Time Signature len == " + mevent.Metalength +
                                        " != 4", file.GetOffset());
                    } else {
                        mevent.Numerator = ((byte) mevent.Value[0]);
                        mevent.Denominator = ((byte) Math.pow(2, mevent.Value[1]));
                    }
                } else if (mevent.Metaevent == MetaEventTempo) {
                    if (mevent.Metalength != 3) {
                        throw new MidiFileException(
                                "Meta Event Tempo len == " + mevent.Metalength +
                                        " != 3", file.GetOffset());
                    }
                    mevent.Tempo = ((mevent.Value[0] & 0xFF) << 16) |
                            ((mevent.Value[1] & 0xFF) << 8) |
                            (mevent.Value[2] & 0xFF);
                } else if (mevent.Metaevent == MetaEventEndOfTrack) {
                    /* break;  */
                } else {
                    System.out.println("MidiPlayerUtils " + new String(mevent.Value));
                }
            } else {
                throw new MidiFileException("Unknown event " + mevent.EventFlag,
                        file.GetOffset() - 1);
            }
        }

        return result;
    }

    public static MidiPlayerUtils getInstance() {
        if (instance == null) {
            Class var0 = MidiPlayerUtils.class;
            synchronized (MidiPlayerUtils.class) {
                if (instance == null) {
                    instance = new MidiPlayerUtils();
                }
            }
        }
        return instance;
    }


    private float currentTempo = 0;

    /**
     * 播放
     */
    public void play(long startPosition, float tempo) {
        currentStartPosition = startPosition;
        currentTempo = tempo;
        new Handler().post(doPlay);
    }

    public void stopPlay() {
        Log.i(TAG, "stopPlay: ");
        stopSound();
    }

    public void seekTo(long time) {
        BassMusicPlay.getInstance().Seek(time);
    }

    public boolean setTracksVolume(int[] tracks, float volume) {
        //切换的时候 保持静音
        if (hasNoteTrackNum > 0) {
            for (int i = 0; i < tracks.length; i++) {
                if (!BassMusicPlay.getInstance().SetTrackVolume2(tracks[i], volume)) {
                    return false;
                }
            }
            return true;
        } else {
            return false;
        }
    }

    public boolean setTrackVolume(int tracks, float volume) {
        //切换的时候 保持静音
        if (hasNoteTrackNum > 0) {
            return BassMusicPlay.getInstance().SetTrackVolume2(tracks, volume);
        } else {
            return false;
        }
    }
    public float getTrackVolume(int track){
        return BassMusicPlay.getInstance().GetTrackVolume(track);
    }

    public boolean setTrackVolumeByName(String trackName, float volume) {
        if (tracks == null) {
            return false;
        }
        for (int i = 0; i < tracks.size(); i++) {
            if (!TextUtils.isEmpty(tracks.get(i).getTrackName()) && !TextUtils.isEmpty(trackName)) {
                if (tracks.get(i).getTrackName().trim().equalsIgnoreCase(trackName.trim())) {
                    return setTrackVolume(i, volume);
                }
            }
        }
        return false;
    }

    public void reset() {
        stopSound();
    }

    public void destroy() {
        stopSound();
    }

    public void pausePlay() {
        BassMusicPlay.getInstance().Pause();
    }

    public void resumePlay() {
        BassMusicPlay.getInstance().Resume();
    }

    public boolean isPlaying() {
        return BassMusicPlay.getInstance().IsPlaying();
    }

    public boolean isPause() {
        return BassMusicPlay.getInstance().IsPause();
    }

    public boolean isStop() {
        return BassMusicPlay.getInstance().IsStop();
    }

    public boolean setNoteFineTune(int trackNum, int targetHz) {
        return BassMusicPlay.getInstance().setNoteFineTune(trackNum, targetHz);
    }
    /**
     * 获取当前播放进度
     *
     * @return
     */
    public long getCurrentPosition() {
        return BassMusicPlay.getInstance().GetCurrentPosition();
    }

    public long getTotalLength() {
        return BassMusicPlay.getInstance().GetTotalLength();
    }

    public double getTotalLength2Second() {
        return BassMusicPlay.getInstance().getTotalLength2Second();
    }
    public void setCompletionListener(PlayerListener.OnCompletionListener listener) {
        BassMusicPlay.getInstance().SetOnCompletionListener(listener);
    }

    public void setErrorListener(PlayerListener.OnErrorListener listener) {
        BassMusicPlay.getInstance().SetOnErrorListener(listener);
    }

    public void setPreparedListener(PlayerListener.OnPreparedListener listener) {
        BassMusicPlay.getInstance().SetOnPreparedListener(listener);
    }

    private void stopSound() {
        BassMusicPlay.getInstance().Stop();

    }

    public void setSpeed(float speed) {
        BassMusicPlay.getInstance().setSpeed(speed);
    }

    private Runnable doPlay = new Runnable() {
        @Override
        public void run() {
            playSound(mContext,  currentTempo);
            return;
        }
    };


    private void playSound(Context context, float speed) {
        if (BassMusicPlay.getInstance().LoadSoundFont(context.getApplicationContext().getExternalFilesDir("colexiu") + "/dysf.sf2")) {
            if (currentStartPosition != 0) {
                BassMusicPlay.getInstance().Seek(currentStartPosition);
            }
            BassMusicPlay.getInstance().setSpeed(speed);
            BassMusicPlay.getInstance().Play();
        }


    }

    /**
     * 提供给h5调用的midi播放
     * @param speed
     * @param hertz
     * @param listener
     * @param compleListener
     */
    public void playSound(float speed, int hertz, PlayerListener.OnPreparedListener listener, PlayerListener.OnCompletionListener compleListener) {
        if (listener != null) {
            BassMusicPlay.getInstance().SetOnPreparedListener(listener);
        }
        if (compleListener != null) {
            BassMusicPlay.getInstance().SetOnCompletionListener(compleListener);
        }
        if (BassMusicPlay.getInstance().LoadSoundFont(mContext.getApplicationContext().getExternalFilesDir("colexiu") + "/dysf.sf2")) {
            if (currentStartPosition != 0) {
                BassMusicPlay.getInstance().Seek(currentStartPosition);
            }
            for(int i=0; i<tracks.size(); i++){
                BassMusicPlay.getInstance().setNoteFineTune(i, hertz);
            }
            BassMusicPlay.getInstance().setSpeed(speed);
            BassMusicPlay.getInstance().Play();

        }


    }

}
