package ioio.lib.util;

import ioio.lib.api.IOIO;
import ioio.lib.api.IOIOFactory;
import ioio.lib.api.exception.ConnectionLostException;
import ioio.lib.api.exception.IncompatibilityException;
import ioio.lib.impl.Version;
import ioio.lib.spi.IOIOConnectionFactory;
import ioio.lib.spi.Log;
import ioio.lib.util.IOIOConnectionManager.IOIOConnectionThreadProvider;
import ioio.lib.util.IOIOConnectionManager.Thread;

public abstract class IOIOBaseApplicationHelper implements IOIOConnectionThreadProvider {
    private static final String TAG = "IOIOBaseApplicationHelper";

    static {
        Log.i(TAG, "Using IOIOLib version: " + Version.get());
    }

    protected final IOIOLooperProvider looperProvider_;

    public IOIOBaseApplicationHelper(IOIOLooperProvider provider) {
        looperProvider_ = provider;
    }

    @Override
    public Thread createThreadFromFactory(IOIOConnectionFactory factory) {
        IOIOLooper looper = looperProvider_.createIOIOLooper(factory.getType(),
                factory.getExtra());
        if (looper == null) {
            return null;
        }
        return new IOIOThread(looper, factory);
    }

    /**
     * A thread, dedicated for communication with a single physical IOIO device.
     */
    protected static class IOIOThread extends IOIOConnectionManager.Thread {
        private final IOIOLooper looper_;
        private final IOIOConnectionFactory connectionFactory_;
        protected IOIO ioio_;
        private boolean abort_ = false;
        private boolean connected_ = false;

        IOIOThread(IOIOLooper looper, IOIOConnectionFactory factory) {
            looper_ = looper;
            connectionFactory_ = factory;
        }

        /*
         * (non-Javadoc)
         *
         * @see ioio.lib.util.IOIOConnectionThread#run()
         */
        @Override
        public final void run() {
            super.run();
            while (!abort_) {
                try {
                    synchronized (this) {
                        if (abort_) {
                            break;
                        }
                        ioio_ = IOIOFactory.create(connectionFactory_
                                .createConnection());
                    }
                } catch (Exception e) {
                    Log.e(TAG, "Failed to create IOIO, aborting IOIOThread!");
                    return;
                }
                // if we got here, we have a ioio_!
                try {
                    ioio_.waitForConnect();
                    connected_ = true;
                    looper_.setup(ioio_);
                    while (!abort_ && ioio_.getState() == IOIO.State.CONNECTED) {
                        looper_.loop();
                    }
                } catch (ConnectionLostException ignored) {
                } catch (InterruptedException e) {
                    ioio_.disconnect();
                } catch (IncompatibilityException e) {
                    Log.e(TAG, "Incompatible IOIO firmware", e);
                    looper_.incompatible(ioio_);
                    // nothing to do - just wait until physical
                    // disconnection
                } catch (Exception e) {
                    Log.e(TAG, "Unexpected exception caught", e);
                    ioio_.disconnect();
                    break;
                } finally {
                    try {
                        ioio_.waitForDisconnect();
                    } catch (InterruptedException ignored) {
                    }
                    synchronized (this) {
                        ioio_ = null;
                    }
                    if (connected_) {
                        looper_.disconnected();
                        connected_ = false;
                    }
                }
            }
            Log.d(TAG, "IOIOThread is exiting");
        }

        /*
         * (non-Javadoc)
         *
         * @see ioio.lib.util.IOIOConnectionThread#abort()
         */
        @Override
        public synchronized final void abort() {
            abort_ = true;
            if (ioio_ != null) {
                ioio_.disconnect();
            }
            if (connected_) {
                interrupt();
            }
        }
    }

}