/*
 * Copyright 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.mobileer.oboetester;

import android.Manifest;
import android.content.pm.PackageManager;
import android.media.midi.MidiDevice;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiInputPort;
import android.media.midi.MidiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.mobileer.miditools.MidiOutputPortConnectionSelector;
import com.mobileer.miditools.MidiPortConnector;
import com.mobileer.miditools.MidiTools;

import java.io.IOException;

import static com.mobileer.oboetester.MidiTapTester.NoteListener;
import static com.mobileer.oboetester.TapToToneTester.TestResult;

public class TapToToneActivity extends TestOutputActivityBase {
    private static final int MY_PERMISSIONS_REQUEST_RECORD_AUDIO = 1234;
    private MidiManager mMidiManager;
    private MidiInputPort mInputPort;

    protected MidiTapTester mMidiTapTester;
    protected TapToToneTester mTapToToneTester;

    private Button mStopButton;
    private Button mStartButton;

    private MidiOutputPortConnectionSelector mPortSelector;
    private final MyNoteListener mTestListener = new MyNoteListener();

    @Override
    protected void inflateActivity() {
        setContentView(R.layout.activity_tap_to_tone);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mAudioOutTester = addAudioOutputTester();

        mTapToToneTester = new TapToToneTester(this,
                getResources().getString(R.string.tap_to_tone_instructions));

        if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI)) {
            setupMidi();
        } else {
            Toast.makeText(TapToToneActivity.this,
                    "MIDI not supported!", Toast.LENGTH_LONG)
                    .show();
        }


        // Start a blip test when the waveform view is tapped.
        WaveformView mWaveformView = (WaveformView) findViewById(R.id.waveview_audio);
        mWaveformView.setOnTouchListener((view, event) -> {
            // Do not call view.performClick() because it may trigger a touch sound!
            int action = event.getActionMasked();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_POINTER_DOWN:
                    trigger();
                    break;
                case MotionEvent.ACTION_MOVE:
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_POINTER_UP:
                    break;
            }
            // Must return true or we do not get the ACTION_MOVE and
            // ACTION_UP events.
            return true;
        });

        mStartButton = (Button) findViewById(R.id.button_start);
        mStopButton = (Button) findViewById(R.id.button_stop);
        updateButtons(false);

        updateEnabledWidgets();
    }

    private void updateButtons(boolean running) {
        mStartButton.setEnabled(!running);
        mStopButton.setEnabled(running);
    }

    void trigger() {
        if (mTapToToneTester.isArmed()) {
            mAudioOutTester.trigger();
            mTapToToneTester.analyzeLater(getString(R.string.please_wait));
        } else {
            showToast(getString(R.string.no_double_tap));
        }
    }

    @Override
    int getActivityType() {
        return ACTIVITY_TAP_TO_TONE;
    }

    @Override
    protected void onDestroy() {
        mMidiTapTester.removeTestListener(mTestListener);
        closeMidiResources();
        super.onDestroy();
    }

    private void setupMidi() {
        // Setup MIDI
        mMidiManager = (MidiManager) getSystemService(MIDI_SERVICE);
        MidiDeviceInfo[] infos = mMidiManager.getDevices();

        // Open the port now so that the AudioMidiTester gets created.
        for (MidiDeviceInfo info : infos) {
            Bundle properties = info.getProperties();
            String product = properties
                    .getString(MidiDeviceInfo.PROPERTY_PRODUCT);

            Log.i(TAG, "product = " + product);
            if ("AudioLatencyTester".equals(product)) {
                openPort(info);
                break;
            }
        }

    }

    // These should only be set after mAudioMidiTester is set.
    private void setSpinnerListeners() {
        MidiDeviceInfo synthInfo = MidiTools.findDevice(mMidiManager, "AndroidTest",
                "AudioLatencyTester");
        Log.i(TAG, "found tester virtual device info: " + synthInfo);
        int portIndex = 0;
        mPortSelector = new MidiOutputPortConnectionSelector(mMidiManager, this,
                R.id.spinner_synth_sender, synthInfo, portIndex);
        mPortSelector.setConnectedListener(new MyPortsConnectedListener());

    }

    private class MyNoteListener implements NoteListener {
        @Override
        public void onNoteOn(final int pitch) {
            runOnUiThread(() -> {
                trigger();
                mStreamContexts.get(0).configurationView.setStatusText("MIDI pitch = " + pitch);
            });
        }
    }


    private void openPort(final MidiDeviceInfo info) {
        mMidiManager.openDevice(info, device -> {
            if (device == null) {
                Log.e(TAG, "could not open device " + info);
            } else {
                mInputPort = device.openInputPort(0);
                Log.i(TAG, "opened MIDI port = " + mInputPort + " on " + info);
                mMidiTapTester = MidiTapTester.getInstance();

                Log.i(TAG, "openPort() mAudioMidiTester = " + mMidiTapTester);
                // Now that we have created the AudioMidiTester, close the port so we can
                // open it later.
                try {
                    mInputPort.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                mMidiTapTester.addTestListener(mTestListener);

                setSpinnerListeners();
            }
        }, new Handler(Looper.getMainLooper())
        );
    }

    // TODO Listen to the synth server
    // for open/close events and then disable/enable the spinner.
    private class MyPortsConnectedListener
            implements MidiPortConnector.OnPortsConnectedListener {
        @Override
        public void onPortsConnected(final MidiDevice.MidiConnection connection) {
            Log.i(TAG, "onPortsConnected, connection = " + connection);
            runOnUiThread(() -> {
                if (connection == null) {
                    Toast.makeText(TapToToneActivity.this,
                            R.string.error_port_busy, Toast.LENGTH_LONG)
                            .show();
                    mPortSelector.clearSelection();
                } else {
                    Toast.makeText(TapToToneActivity.this,
                            R.string.port_open_ok, Toast.LENGTH_LONG)
                            .show();
                }
            });
        }
    }

    private void closeMidiResources() {
        if (mPortSelector != null) {
            mPortSelector.close();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    private boolean hasRecordAudioPermission(){
        boolean hasPermission = (checkSelfPermission(
                Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED);
        Log.i(TAG, "Has RECORD_AUDIO permission? " + hasPermission);
        return hasPermission;
    }

    private void requestRecordAudioPermission(){

        String requiredPermission = Manifest.permission.RECORD_AUDIO;

        // If the user previously denied this permission then show a message explaining why
        // this permission is needed
        if (shouldShowRequestPermissionRationale(requiredPermission)) {
            showErrorToast("This app needs to record audio through the microphone....");
        }

        // request the permission.
        requestPermissions(new String[]{requiredPermission},
                MY_PERMISSIONS_REQUEST_RECORD_AUDIO);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           String[] permissions,
                                           int[] grantResults) {
        if (MY_PERMISSIONS_REQUEST_RECORD_AUDIO != requestCode) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            return;
        }

        if (grantResults.length != 1 ||
                grantResults[0] != PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(getApplicationContext(),
                    getString(R.string.need_record_audio_permission),
                    Toast.LENGTH_SHORT)
                    .show();
        } else {
            startAudioPermitted();
        }
    }

    public void startTest(View view) {
        try {
            openAudio();
        } catch (IOException e) {
            e.printStackTrace();
            showErrorToast("Open audio failed!");
            return;
        }
        if (hasRecordAudioPermission()) {
            startAudioPermitted();
        } else {
            requestRecordAudioPermission();
        }
    }

    private void startAudioPermitted() {
        try {
            super.startAudio();
            mTapToToneTester.resetLatency();
            mTapToToneTester.start();
            updateButtons(true);
        } catch (IOException e) {
            e.printStackTrace();
            showErrorToast("Start audio failed! " + e.getMessage());
            return;
        }
    }

    public void stopTest(View view) {
        mTapToToneTester.stop();
        stopAudio();
        closeAudio();
        updateButtons(false);
    }
}
