/*
 * Copyright (C) 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.example.android.common.midi;

import android.app.Activity;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiDeviceStatus;
import android.media.midi.MidiManager;
import android.media.midi.MidiManager.DeviceCallback;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;

import java.util.HashSet;

/**
 * Base class that uses a Spinner to select available MIDI ports.
 */
public abstract class MidiPortSelector extends DeviceCallback {
    private int mType = MidiDeviceInfo.PortInfo.TYPE_INPUT;
    protected ArrayAdapter<MidiPortWrapper> mAdapter;
    protected HashSet<MidiPortWrapper> mBusyPorts = new HashSet<MidiPortWrapper>();
    private Spinner mSpinner;
    protected MidiManager mMidiManager;
    protected Activity mActivity;
    private MidiPortWrapper mCurrentWrapper;

    /**
     * @param midiManager
     * @param activity
     * @param spinnerId
     *            ID from the layout resource
     * @param type
     *            TYPE_INPUT or TYPE_OUTPUT
     */
    public MidiPortSelector(MidiManager midiManager, Activity activity,
            int spinnerId, int type) {
        mMidiManager = midiManager;
        mActivity = activity;
        mType = type;
        mAdapter = new ArrayAdapter<MidiPortWrapper>(activity,
                android.R.layout.simple_spinner_item);
        mAdapter.setDropDownViewResource(
                android.R.layout.simple_spinner_dropdown_item);
        mAdapter.add(new MidiPortWrapper(null, 0, 0));

        mSpinner = (Spinner) activity.findViewById(spinnerId);
        mSpinner.setOnItemSelectedListener(
                new AdapterView.OnItemSelectedListener() {

                    public void onItemSelected(AdapterView<?> parent, View view,
                            int pos, long id) {
                        mCurrentWrapper = mAdapter.getItem(pos);
                        onPortSelected(mCurrentWrapper);
                    }

                    public void onNothingSelected(AdapterView<?> parent) {
                        onPortSelected(null);
                        mCurrentWrapper = null;
                    }
                });
        mSpinner.setAdapter(mAdapter);

        mMidiManager.registerDeviceCallback(this,
                new Handler(Looper.getMainLooper()));

        MidiDeviceInfo[] infos = mMidiManager.getDevices();
        for (MidiDeviceInfo info : infos) {
            onDeviceAdded(info);
        }
    }

    /**
     * Set to no port selected.
     */
    public void clearSelection() {
        mSpinner.setSelection(0);
    }

    private int getInfoPortCount(final MidiDeviceInfo info) {
        int portCount = (mType == MidiDeviceInfo.PortInfo.TYPE_INPUT)
                ? info.getInputPortCount() : info.getOutputPortCount();
        return portCount;
    }

    @Override
    public void onDeviceAdded(final MidiDeviceInfo info) {
        int portCount = getInfoPortCount(info);
        for (int i = 0; i < portCount; ++i) {
            MidiPortWrapper wrapper = new MidiPortWrapper(info, mType, i);
            mAdapter.add(wrapper);
            Log.i(MidiConstants.TAG, wrapper + " was added");
            mAdapter.notifyDataSetChanged();
        }
    }

    @Override
    public void onDeviceRemoved(final MidiDeviceInfo info) {
        int portCount = getInfoPortCount(info);
        for (int i = 0; i < portCount; ++i) {
            MidiPortWrapper wrapper = new MidiPortWrapper(info, mType, i);
            MidiPortWrapper currentWrapper = mCurrentWrapper;
            mAdapter.remove(wrapper);
            // If the currently selected port was removed then select no port.
            if (wrapper.equals(currentWrapper)) {
                clearSelection();
            }
            mAdapter.notifyDataSetChanged();
            Log.i(MidiConstants.TAG, wrapper + " was removed");
        }
    }

    @Override
    public void onDeviceStatusChanged(final MidiDeviceStatus status) {
        // If an input port becomes busy then remove it from the menu.
        // If it becomes free then add it back to the menu.
        if (mType == MidiDeviceInfo.PortInfo.TYPE_INPUT) {
            MidiDeviceInfo info = status.getDeviceInfo();
            Log.i(MidiConstants.TAG, "MidiPortSelector.onDeviceStatusChanged status = " + status
                    + ", mType = " + mType
                    + ", activity = " + mActivity.getPackageName()
                    + ", info = " + info);
            // Look for transitions from free to busy.
            int portCount = info.getInputPortCount();
            for (int i = 0; i < portCount; ++i) {
                MidiPortWrapper wrapper = new MidiPortWrapper(info, mType, i);
                if (!wrapper.equals(mCurrentWrapper)) {
                    if (status.isInputPortOpen(i)) { // busy?
                        if (!mBusyPorts.contains(wrapper)) {
                            // was free, now busy
                            mBusyPorts.add(wrapper);
                            mAdapter.remove(wrapper);
                            mAdapter.notifyDataSetChanged();
                        }
                    } else {
                        if (mBusyPorts.remove(wrapper)) {
                            // was busy, now free
                            mAdapter.add(wrapper);
                            mAdapter.notifyDataSetChanged();
                        }
                    }
                }
            }
        }
    }

    /**
     * Implement this method to handle the user selecting a port on a device.
     *
     * @param wrapper
     */
    public abstract void onPortSelected(MidiPortWrapper wrapper);

    /**
     * Implement this method to clean up any open resources.
     */
    public abstract void onClose();

    /**
     *
     */
    public void close() {
        onClose();
    }
}