package jp.kshoji.blemidi.sample;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.wearable.activity.WearableActivity;
import android.support.wearable.view.WatchViewStub;
import android.view.View;
import android.widget.TextView;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import jp.kshoji.blemidi.central.BleMidiCentralProvider;
import jp.kshoji.blemidi.device.MidiInputDevice;
import jp.kshoji.blemidi.device.MidiOutputDevice;
import jp.kshoji.blemidi.listener.OnMidiDeviceAttachedListener;
import jp.kshoji.blemidi.listener.OnMidiDeviceDetachedListener;
import jp.kshoji.blemidi.listener.OnMidiInputEventListener;
import jp.kshoji.blemidi.sample.util.SoundMaker;
import jp.kshoji.blemidi.sample.util.Tone;

public class SynthesizerActivity extends WearableActivity implements OnMidiInputEventListener {

    private TextView textView;
    private TextView noteView;
    private TextView titleView;
    private View background;

    // Play sounds
    AudioTrack audioTrack;
    Timer timer;
    TimerTask timerTask;
    SoundMaker soundMaker;
    final Set<Tone> tones = new HashSet<>();
    int currentProgram = 0;

    BleMidiCentralProvider bleMidiCentralProvider;
    private String[] notenames;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_synthesizer);

        final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub);
        stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
            @Override
            public void onLayoutInflated(WatchViewStub stub) {
                textView = (TextView) stub.findViewById(R.id.text);
                titleView = (TextView) stub.findViewById(R.id.title);
                background = stub.findViewById(R.id.background);
                noteView = (TextView) stub.findViewById(R.id.note);
            }
        });

        bleMidiCentralProvider = new BleMidiCentralProvider(this);

        notenames = getResources().getStringArray(R.array.notenames);

        setAmbientEnabled();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        bleMidiCentralProvider = null;

        textView = null;
        titleView = null;
        background = null;
    }

    @Override
    protected void onResume() {
        super.onResume();

        bleMidiCentralProvider.setOnMidiDeviceAttachedListener(new OnMidiDeviceAttachedListener() {
            @Override
            public void onMidiInputDeviceAttached(@NonNull MidiInputDevice midiInputDevice) {
                midiInputDevice.setOnMidiInputEventListener(SynthesizerActivity.this);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (textView != null) {
                            textView.setText(getString(R.string.connected));
                        }
                    }
                });
            }

            @Override
            public void onMidiOutputDeviceAttached(@NonNull final MidiOutputDevice midiOutputDevice) {
                // do nothing
            }
        });

        bleMidiCentralProvider.setOnMidiDeviceDetachedListener(new OnMidiDeviceDetachedListener() {
            @Override
            public void onMidiInputDeviceDetached(@NonNull MidiInputDevice midiInputDevice) {
                midiInputDevice.setOnMidiInputEventListener(null);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (bleMidiCentralProvider == null) {
                            return;
                        }

                        if (bleMidiCentralProvider.getMidiInputDevices().size() <= 1) {
                            if (textView != null) {
                                textView.setText(getString(R.string.no_devices_found));
                            }
                        }

                        noteView.setText("");
                    }
                });
            }

            @Override
            public void onMidiOutputDeviceDetached(@NonNull final MidiOutputDevice midiOutputDevice) {
                // do nothing
            }
        });

        bleMidiCentralProvider.startScanDevice(-1);

        soundMaker = SoundMaker.getInstance();
        final int bufferSize = AudioTrack.getMinBufferSize(soundMaker.getSamplingRate(), AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
        int timerRate = bufferSize * 1000 / soundMaker.getSamplingRate() / 2;
        final short[] wav = new short[bufferSize / 2];

        audioTrack = prepareAudioTrack(soundMaker.getSamplingRate());
        timer = new Timer();
        timerTask = new TimerTask() {
            @Override
            public void run() {
                if (soundMaker != null) {
                    synchronized (tones) {
                        for (int i = 0; i < wav.length; i++) {
                            wav[i] = (short) (soundMaker.makeWaveStream(tones) * 1024);
                        }
                    }
                    try {
                        if (audioTrack != null) {
                            audioTrack.write(wav, 0, wav.length);
                        }
                    } catch (IllegalStateException | NullPointerException e) {
                        // do nothing
                    }
                }
            }
        };
        timer.scheduleAtFixedRate(timerTask, 10, timerRate);
    }

    @Override
    public void onEnterAmbient(Bundle ambientDetails) {
        super.onEnterAmbient(ambientDetails);

        textView.getPaint().setAntiAlias(false);
        titleView.getPaint().setAntiAlias(false);
        noteView.getPaint().setAntiAlias(false);
        background.setBackgroundColor(0);
    }

    @Override
    public void onExitAmbient() {
        super.onExitAmbient();

        textView.getPaint().setAntiAlias(true);
        titleView.getPaint().setAntiAlias(true);
        noteView.getPaint().setAntiAlias(true);
        background.setBackgroundColor(0xff80a0f0);
    }

    /**
     * @param samplingRate sampling rate for playing
     * @return configured {@link AudioTrack} instance
     */
    private static AudioTrack prepareAudioTrack(int samplingRate) {
        AudioTrack result = new AudioTrack(AudioManager.STREAM_MUSIC, samplingRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, AudioTrack.getMinBufferSize(samplingRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT), AudioTrack.MODE_STREAM);
        result.setStereoVolume(1f, 1f);
        result.play();
        return result;
    }

    @Override
    protected void onPause() {
        super.onPause();

        bleMidiCentralProvider.stopScanDevice();

        if (timer != null) {
            try {
                timer.cancel();
                timer.purge();
            } catch (Throwable t) {
                // do nothing
            } finally {
                timer = null;
            }
        }
        if (audioTrack != null) {
            try {
                audioTrack.stop();
                audioTrack.flush();
                audioTrack.release();
            } catch (Throwable t) {
                // do nothing
            } finally {
                audioTrack = null;
            }
        }
    }

    @Override
    public void onMidiSystemExclusive(@NonNull MidiInputDevice midiInputDevice, @NonNull byte[] bytes) {

    }

    @Override
    public void onMidiNoteOff(@NonNull MidiInputDevice midiInputDevice, int channel, int note, int velocity) {
        synchronized (tones) {
            Iterator<Tone> it = tones.iterator();
            while (it.hasNext()) {
                Tone tone = it.next();
                if (tone.getNote() == note) {
                    it.remove();
                }
            }

            if (tones.size() < 1) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        noteView.setText("");
                    }
                });
            }
        }
    }

    @Override
    public void onMidiNoteOn(@NonNull MidiInputDevice midiInputDevice, int channel, final int note, int velocity) {
        synchronized (tones) {
            if (velocity == 0) {
                Iterator<Tone> it = tones.iterator();
                while (it.hasNext()) {
                    Tone tone = it.next();
                    if (tone.getNote() == note) {
                        it.remove();
                    }
                }

                if (tones.size() < 1) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            noteView.setText("");
                        }
                    });
                }
            } else {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        noteView.setText(notenames[note % 12]);
                    }
                });
                tones.add(new Tone(note, velocity / 127.0, currentProgram));
            }
        }
    }

    @Override
    public void onMidiPolyphonicAftertouch(@NonNull MidiInputDevice midiInputDevice, int i, int i1, int i2) {

    }

    @Override
    public void onMidiControlChange(@NonNull MidiInputDevice midiInputDevice, int i, int i1, int i2) {

    }

    @Override
    public void onMidiProgramChange(@NonNull MidiInputDevice midiInputDevice, int channel, int program) {
        currentProgram = program % Tone.FORM_MAX;
        synchronized (tones) {
            for (Tone tone : tones) {
                tone.setForm(currentProgram);
            }
        }
    }

    @Override
    public void onMidiChannelAftertouch(@NonNull MidiInputDevice midiInputDevice, int i, int i1) {

    }

    @Override
    public void onMidiPitchWheel(@NonNull MidiInputDevice midiInputDevice, int i, int i1) {

    }

    @Override
    public void onMidiTimeCodeQuarterFrame(@NonNull MidiInputDevice midiInputDevice, int i) {

    }

    @Override
    public void onMidiSongSelect(@NonNull MidiInputDevice midiInputDevice, int i) {

    }

    @Override
    public void onMidiSongPositionPointer(@NonNull MidiInputDevice midiInputDevice, int i) {

    }

    @Override
    public void onMidiTuneRequest(@NonNull MidiInputDevice midiInputDevice) {

    }

    @Override
    public void onMidiTimingClock(@NonNull MidiInputDevice midiInputDevice) {

    }

    @Override
    public void onMidiStart(@NonNull MidiInputDevice midiInputDevice) {

    }

    @Override
    public void onMidiContinue(@NonNull MidiInputDevice midiInputDevice) {

    }

    @Override
    public void onMidiStop(@NonNull MidiInputDevice midiInputDevice) {

    }

    @Override
    public void onMidiActiveSensing(@NonNull MidiInputDevice midiInputDevice) {

    }

    @Override
    public void onMidiReset(@NonNull MidiInputDevice midiInputDevice) {

    }

    @Override
    public void onRPNMessage(@NonNull MidiInputDevice midiInputDevice, int i, int i1, int i2) {

    }

    @Override
    public void onNRPNMessage(@NonNull MidiInputDevice midiInputDevice, int i, int i1, int i2) {

    }
}
