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

import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;

import com.android.internal.midi.MidiDispatcher;

import dalvik.system.CloseGuard;

import libcore.io.IoUtils;

import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * This class is used for receiving data from a port on a MIDI device
 */
public final class MidiOutputPort extends MidiSender implements Closeable {
    private static final String TAG = "MidiOutputPort";

    private IMidiDeviceServer mDeviceServer;
    private final IBinder mToken;
    private final int mPortNumber;
    private final FileInputStream mInputStream;
    private final MidiDispatcher mDispatcher = new MidiDispatcher();

    private final CloseGuard mGuard = CloseGuard.get();
    private boolean mIsClosed;

    // This thread reads MIDI events from a socket and distributes them to the list of
    // MidiReceivers attached to this device.
    private final Thread mThread = new Thread() {
        @Override
        public void run() {
            byte[] buffer = new byte[MidiPortImpl.MAX_PACKET_SIZE];

            try {
                while (true) {
                    // read next event
                    int count = mInputStream.read(buffer);
                    if (count < 0) {
                        // This is the exit condition as read() returning <0 indicates
                        // that the pipe has been closed.
                        break;
                        // FIXME - inform receivers here?
                    }

                    int packetType = MidiPortImpl.getPacketType(buffer, count);
                    switch (packetType) {
                        case MidiPortImpl.PACKET_TYPE_DATA: {
                            int offset = MidiPortImpl.getDataOffset(buffer, count);
                            int size = MidiPortImpl.getDataSize(buffer, count);
                            long timestamp = MidiPortImpl.getPacketTimestamp(buffer, count);

                            // dispatch to all our receivers
                            mDispatcher.send(buffer, offset, size, timestamp);
                            break;
                        }
                        case MidiPortImpl.PACKET_TYPE_FLUSH:
                            mDispatcher.flush();
                            break;
                        default:
                            Log.e(TAG, "Unknown packet type " + packetType);
                            break;
                    }
                } // while (true)
            } catch (IOException e) {
                // FIXME report I/O failure?
                // TODO: The comment above about the exit condition is not currently working
                // as intended. The read from the closed pipe is throwing an error rather than
                // returning <0, so this becomes (probably) not an error, but the exit case.
                // This warrants further investigation;
                // Silence the (probably) spurious error message.
                // Log.e(TAG, "read failed", e);
            } finally {
                IoUtils.closeQuietly(mInputStream);
            }
        }
    };

    /* package */ MidiOutputPort(IMidiDeviceServer server, IBinder token,
            FileDescriptor fd, int portNumber) {
        mDeviceServer = server;
        mToken = token;
        mPortNumber = portNumber;
        mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(new ParcelFileDescriptor(fd));
        mThread.start();
        mGuard.open("close");
    }

    /* package */ MidiOutputPort(FileDescriptor fd, int portNumber) {
        this(null, null, fd, portNumber);
    }

    /**
     * Returns the port number of this port
     *
     * @return the port's port number
     */
    public final int getPortNumber() {
        return mPortNumber;
    }

    @Override
    public void onConnect(MidiReceiver receiver) {
        mDispatcher.getSender().connect(receiver);
    }

    @Override
    public void onDisconnect(MidiReceiver receiver) {
        mDispatcher.getSender().disconnect(receiver);
    }

    @Override
    public void close() throws IOException {
        synchronized (mGuard) {
            if (mIsClosed) return;

            mGuard.close();
            mInputStream.close();
            if (mDeviceServer != null) {
                try {
                    mDeviceServer.closePort(mToken);
                } catch (RemoteException e) {
                    Log.e(TAG, "RemoteException in MidiOutputPort.close()");
                }
            }
            mIsClosed = true;
        }
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            if (mGuard != null) {
                mGuard.warnIfOpen();
            }

            // not safe to make binder calls from finalize()
            mDeviceServer = null;
            close();
        } finally {
            super.finalize();
        }
    }
}
