
package party.bami.eqfix.Musicplayer;

import android.content.Context;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.AudioFormat;

import party.bami.eqfix.analysis.Convolution;
import party.bami.eqfix.analysis.PresetEmptyException;
import party.bami.eqfix.analysis.PresetInvalidFileException;
import party.bami.eqfix.convolution.Convolver;
import party.bami.eqfix.fragment.MediaPlayerFragment;
import party.bami.eqfix.preset.EQFPreset;

import java.io.*;
import java.util.Arrays;


/**
 * This enum represents the possible actions the music player can do.
 */
enum Action {
    NEXT, PREVIOUS, PLAY, PAUSE, Normal, FF, REW, STOP,DONE

}

/**
 * This music-player allows a queue of songs to be displayed.
 * It obtains this music list from the @ParseDirectories class.
 *
 * @See ParseDirectories.java
 */
public class MusicPlayer {

    private static AudioTrack track;
    private static Thread thread;

    public final static int DEFAULT_SAMPLE_RATE = 44100; //Hz
    public final static int DEFAULT_CHANNEL_AMOUNT = 2;
    private static int sampleRate = DEFAULT_SAMPLE_RATE;
    private static int channels = DEFAULT_CHANNEL_AMOUNT;

    private static File[] queue = null;
    private static int queuePosition;

    private static Action previousAction;
    private static Action action;
    MediaPlayerFragment mediaPlayerFragment;

    /**
     * This class implements the thread the
     * AudioTrack will run on.
     * It performs checks for each next action
     * that must be done and plays the queue in order.
     * @See AudioTrack
     */
    class MusicThread implements Runnable {

        /**
         * This run method initializes the queue
         */
        @Override
        public void run() {
            try {
                initTrack();
                playQueue();
            } catch (Exception e) {
                //Do something TODO: BROAD EXCEPTION
            }
        }

        /**
         * This method initializes the AudioTrack so that the next track can be played.
         * It also updates the mediaplayer fragment's wave view to display
         * the new track that is going to be played.
         */
        public void initTrack() {
            mediaPlayerFragment.updateWave();
            int minSize = AudioTrack.getMinBufferSize(44100,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT);
            track = null;
            track = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT, minSize,
                    AudioTrack.MODE_STREAM);
        }

        /**
         * This method plays the queue.
         * It operates within a while loop that continuously checks that the action to be performed
         * is not modified. If it is, it performs the corresponding instructions for that action.
         *
         * Possible actions:
         * - NEXT       - Pause,Flush,Stop and Release the current track. Increment position index by 1 if possible
         * - PLAY       - Write data to AudioTrack while possible.
         * - STOP       - TODO
         * - PAUSE      - Pause the currently playing track.
         * - FF         - Forward the current track 10 seconds.
         * - REW        - Rewind the current track by 10 seconds.
         * - PREVIOUS   - Pause,Flush,Stop and Release the current track. Decrement position index by 1 if possible
         * - DONE       - TODO
         *
         * Upon finishing reading all data, the action is set to Done.
         * Control will then have to call next
         *
         *
         * @See Action
         * @throws IOException
         */
        private void playQueue() throws IOException {
            int offset = 0;
            InputStream is = new FileInputStream(queue[queuePosition]);
            int audioLength = (int) (queue[queuePosition].length() / 2);
            byte[] music = new byte[512];

            Convolver c = null;
            try {
                EQFPreset preset = new EQFPreset(mediaPlayerFragment.getContext());
                preset.load(party.bami.eqfix.other.ParseDirectories.getFilePreset("test.eqf"));
                c = new Convolver(512, preset.getKernel());
                System.out.println("Load: " + Arrays.toString(preset.getKernel()));
            } catch (PresetInvalidFileException|PresetEmptyException|IOException e) {
                e.printStackTrace();
            }

            // Keep going until all data has been read
            while (offset < queue[queuePosition].length()) {
//                System.out.println(action);
//                //audiolength / samplerate = total time
//                System.out.println(audioLength / 2 + " Length ");
//                System.out.println(track.getPlaybackHeadPosition());
//                if (track.getPlaybackHeadPosition() > audioLength / 2) {
//                    System.out.println("Done");
//                    return;
//                }

                switch (action) {
                    case NEXT:
                        track.pause();
                        track.flush();
                        track.stop();
                        track.release();

                        if(queuePosition + 1 == queue.length) {
                            queuePosition = 0;
                        }else{
                            queuePosition++;
                        }

                        action = previousAction;
                        initTrack();
                        playQueue();
                        return;
                    case PLAY:
                        track.play();
                        int numberOfBytesRead = is.read(music);
                        if (numberOfBytesRead != -1) {
                            track.write(music, 0, numberOfBytesRead);
                            offset += numberOfBytesRead;
                        } else {
                            System.out.println("This should Do something");
                            action = Action.DONE;
                        }
                        break;
                    case STOP:
                        break;
                    case PAUSE:
                        track.pause();
                        break;
                    case FF:
                        track.pause();
                        track.flush();
                        track.setPlaybackRate(441000);
                        action = Action.PLAY;
                        break;
                    case REW:
                        track.pause();
                        track.flush();
                        track.setPlaybackRate(44100);
                        action = Action.PLAY;
                        break;
                    case Normal:
                        track.pause();
                        track.flush();
                        track.setPlaybackRate(44100);
                        action = Action.PLAY;
                        break;
                    case PREVIOUS:
                        track.pause();
                        track.flush();
                        track.stop();
                        track.release();
                        if(queuePosition - 1 <= 0) {
                            queuePosition = 0;
                        }else{
                            queuePosition--;
                        }
                        action = previousAction;
                        initTrack();
                        playQueue();
                        return;

                    case DONE:
//                        System.out.println("DONE");
                        break;



                }

            }
            action = Action.DONE;
            // TODO put playqueue here in switch hstatement to prevent lots of nested file
            // TODO Duration indicators
        }
    }


    /**
     * Getter for the currently playing file.
     * @return - File currently being played.
     */
    public File getPlaying() {
        return queue[queuePosition];
    }


    /**
     * This method instantiates a new MusicThread.
     */
    public void playNew() {
        action = Action.PLAY;
        thread = new Thread(new MusicThread());
        thread.start();

    }


    /**
     * Initialize a new mediaplayer/audiotrack object. Currently the audiotrack mode
     * will be set to modestatic
     * TODO: Set mode dynamic
     */
    public MusicPlayer(MediaPlayerFragment mediaPlayerFragment, Context context) {
        this.mediaPlayerFragment = mediaPlayerFragment;
        if (queue == null) {
            queue = ParseDirectories.getAllPlayableMedia(context);
            queuePosition = 0;
            playNew();
        }


    }

    /**
     * This method toggles between
     * playing and pausing the track.
     * If the the thread has died, it creates a new thread.
     */
    public void playPause() {
        if(!thread.isAlive()){
           playNew();
           queuePosition = 0;
        }
        if (pushedForwardOnce){
            pushedForwardOnce = false;
            action = Action.Normal;
        }
        else if (action == Action.PLAY) {
            previousAction = action;
            action = Action.PAUSE;
        } else if(action == Action.PAUSE) {
            previousAction = action;
            action = Action.PLAY;
        }

    }

    /**
     * This method sets the action to NEXT
     * so that the MusicThread can go to the next track.
     */
    public void next() {
        previousAction = action;
        action = Action.NEXT;

    }

    /**
     * This method sets the action to PREVIOUS
     * so that the MusicThread can go to the previous track.
     */
    public void previous() {
        previousAction = action;
        action = Action.PREVIOUS;

    }

    /**
     * This method sets the action to be able to move forward in time
     * so that the MusicThread can skip 10 seconds.
     */
    boolean pushedForwardOnce = false;
    public void forward10seconds() {
        previousAction = action;
        if (!pushedForwardOnce){
            pushedForwardOnce = true;
            action = Action.FF;
        }
        else{
            pushedForwardOnce = false;
            action = Action.Normal;
        }
    }


    /**
     * This method sets the action to be able to go back in time
     * so that the MusicThread can go back in time by 10 seconds.
     */
    public void rewind10seconds() {
        previousAction = action;
        action = Action.REW;

    }

    // TODO: LATER
    public void stop() {
        if (track != null) {
            track.stop();
            track.flush();
            track.release();
        }
    }



//    public void loadMp3(String filePath) {
////
////            int intSize = android.media.AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_CONFIGURATION_STEREO,
////                    AudioFormat.ENCODING_PCM_16BIT);
////
////            AudioTrack at = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_CONFIGURATION_STEREO,
////                    AudioFormat.ENCODING_PCM_16BIT, intSize, AudioTrack.MODE_STREAM);
////
////            //Reading the file..
////            int count = 512 * 1024; // 512 kb
////            //        byte[] byteData = null;
////            //        byteData = new byte[(int)count];
////
////            //we can decode correct byte data here
////            byte[] byteData = null;
////        try {
////            byteData = decode_path(filePath, 0, 20000);
////        } catch (IOException e) {
////            e.printStackTrace();
////        }
////
////        int temp =0;
////            at.play();
////            while (temp<byteData.length)
////            {
////                at.write(byteData, temp, count);
////                temp+= count;
////            }
////            at.stop();
////            at.release();
////
////    }
////
////    public static byte[] decode_path(String path, int startMs, int maxMs)
////            throws IOException{
////        ByteArrayOutputStream outStream = new ByteArrayOutputStream(1024);
////
////        float totalMs = 0;
////        boolean seeking = true;
////
////        File file = new File(path);
////        InputStream inputStream = new BufferedInputStream(new FileInputStream(file), 8 * 1024);
////        try {
////            Bitstream bitstream = new Bitstream(inputStream);
////            Decoder decoder = new Decoder();
////
////            boolean done = false;
////            while (! done) {
////                Header frameHeader = bitstream.readFrame();
////                if (frameHeader == null) {
////                    done = true;
////                } else {
////                    totalMs += frameHeader.ms_per_frame();
////
////                    if (totalMs >= startMs) {
////                        seeking = false;
////                    }
////
////                    if (! seeking) {
////                        SampleBuffer output = (SampleBuffer) decoder.decodeFrame(frameHeader, bitstream);
////
////                        if (output.getSampleFrequency() != 44100
////                                || output.getChannelCount() != 2) {
////                            throw new IllegalArgumentException("mono or non-44100 MP3 not supported");
////                        }
////
////                        short[] pcm = output.getBuffer();
////                        for (short s : pcm) {
////                            outStream.write(s & 0xff);
////                            outStream.write((s >> 8 ) & 0xff);
////                        }
////                    }
////
////                    if (totalMs >= (startMs + maxMs)) {
////                        done = true;
////                    }
////                }
////                bitstream.closeFrame();
////            }
////
////            return outStream.toByteArray();
////        } catch (BitstreamException e) {
////            throw new IOException("Bitstream error: " + e);
////        } catch (DecoderException e) {
////            Log.w(TAG, "Decoder error", e);
////            throw new IOException("Decoder error: " + e);
////        }
////    }
//

}

/**
 * This exception is thrown upon
 * a request being made to the mediaplayer while no audio is loaded.
 */
class NoAudioLoaded extends Exception {

    public NoAudioLoaded(String message) {
        super(message);
    }

}
