package fr.umlv.nxtpilot.bluetooth;



import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;

import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
import fr.umlv.nxtpilot.core.MenuActivity;

/**
 * The Service is used to connect to a NXT Robot. It handles a bluetooth connection to a NXT Bot. 
 * The Service can be bound from an Activity to be used.
 */
public class BluetoothCommService extends Service {

	private static final String TAG = "ConServ";
	private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
	public static final int  IGNORE_MESSAGES=0;
	public static final int  NOTIFY_MESSAGES=1;
	public static final int STATE_CONNECTING = 1;
	public static final int STATE_NONE = 0;
	public static final int STATE_CONNECTED = 2;
   
    
	
	private   final LocalBinder b = new LocalBinder();
	private int mState;
	private ConnectThread mConnectThread;
	private ConnectedThread mConnectedThread;
	private BluetoothAdapter mAdapter;
	private Handler mHandler;
	//private BluetoothSocket mSocket;
	//private BluetoothDevice mDevice;
	private boolean initialized;
	
	@Override
	public IBinder onBind(Intent arg0) {
		  Log.d(TAG, "BIND");
		return b;
	}
	
	@Override
	public boolean onUnbind(Intent intent) {
	    Log.d(TAG, "UNBIND");
	    return true;
	}



	/**
	 * Initialize the Service with the given Handler
	 * @param hand the handler given by an activity
	 */
	public void initialize(Handler hand)
	{
		mHandler=hand;
		if(!initialized)
		{
		
		 mAdapter = BluetoothAdapter.getDefaultAdapter();
	        mState = STATE_NONE;
		}
	}
	
	/**
	 * Create a bluetooth connection to a bluetooth device. 
	 * It will start a thread which will start the connection to the device.
	 * The return from this method doesn't mean that the connection is done or have failed.
	 * @param device the device to be connected
	 */
	  public void connect(BluetoothDevice device) {
	
	     Log.d(TAG, "connect to: " + device);
	     
	
		
	    // Cancel any thread attempting to make a connection
	    if (mState == STATE_CONNECTING) {
	        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
	    }
	
	    // Cancel any thread currently running a connection
	    if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
	
	    // Start the thread to connect with the given device
	    mConnectThread = new ConnectThread(device);
	    mConnectThread.start();
	    setState(STATE_CONNECTING);
		
	}
	  
	  /**
	   * This Method is called when the connection is done(ie the ConnectThread gone well) .
	   * It will launch a new Thread which will handle the reading from the socket.
	   * @param socket the socket to the bluetooth device.
	   * @param device The bluetooth device which is connected
	   */
	    private synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
	        Log.d(TAG, "connected");

	        // Cancel the thread that completed the connection
	        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}

	        // Cancel any thread currently running a connection
	        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

	       

	        // Start the thread to manage the connection and perform transmissions
	        mConnectedThread = new ConnectedThread(socket);
	        mConnectedThread.start();

	        // Send the name of the connected device back to the UI Activity
	        Message msg = mHandler.obtainMessage(MenuActivity.MESSAGE_DEVICE_NAME);
	        Bundle bundle = new Bundle();
	        bundle.putString(MenuActivity.DEVICE_NAME, device.getName());
	        msg.setData(bundle);
	        mHandler.sendMessage(msg);

	        setState(STATE_CONNECTED);
	    }

	    /**
	     * Stop all threads.
	     */
	    public synchronized void cancel() {
	       Log.d(TAG, "stop");
	        if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
	        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
	    
	        setState(STATE_NONE);
	       
	    }
	    
	    
	    
	    /**
	     * Set's the state of the connection.
	     * The handlers used to communicate with activities are informed that the state changed
	     * @param state the new connection state
	     */
	    private synchronized void setState(int state) {
	      Log.d(TAG, "setState() " + mState + " -> " + state);
	        mState = state;

	        // Give the new state to the Handler so the UI Activity can update
	        mHandler.obtainMessage(MenuActivity.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
	    }

	  /** 
	   * This class is a thread wich will create the connection to the given device.
	   */
	  private class ConnectThread extends Thread {
	        private final BluetoothSocket mmSocket;
	        private final BluetoothDevice mmDevice;

	        /**
	         * Constructor of the ConnectThread
	         * @param device The device wich will be connected
	         */
	        public ConnectThread(BluetoothDevice device) {
	            mmDevice = device;
	            BluetoothSocket tmp = null;

	            // Get a BluetoothSocket for a connection with the
	            // given BluetoothDevice
	            try {
	                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
	            } catch (IOException e) {
	                Log.e(TAG, "create() failed", e);
	            }
	            mmSocket = tmp;
	        }

	        /**
	         * The main content that will do the connection
	         */
	        @Override
	        public void run() {
	            Log.i(TAG, "BEGIN mConnectThread");
	            setName("ConnectThread");

	            // Always cancel discovery because it will slow down a connection
	            mAdapter.cancelDiscovery();

	            // Make a connection to the BluetoothSocket
	            try {
	                // This is a blocking call and will only return on a
	                // successful connection or an exception
	                mmSocket.connect();
	            } catch (IOException e) {
	                connectionFailed();
	                // Close the socket
	                try {
	                    mmSocket.close();
	                } catch (IOException e2) {
	                    Log.e(TAG, "unable to close() socket during connection failure", e2);
	                }
	                // Start the service over to restart listening mode
	               // BluetoothCommService.this.start();
	                return;
	            }

	            // Reset the ConnectThread because we're done
	            synchronized (BluetoothCommService.this) {
	                mConnectThread = null;
	            }

	            // Start the connected thread
	            connected(mmSocket, mmDevice);
	        }

	        /**
	         * Cancel the connection
	         */
	        public void cancel() {
	            try {
	                mmSocket.close();
	            } catch (IOException e) {
	                Log.e(TAG, "close() of connect socket failed", e);
	            }
	        }
	    }
	  
	  /**
	   * This thread is used when the connection is done. It will handle the read/write on the socket.
	   * The connection is closed after 60 seconds of inactivity
	   *
	   */
	  private class ConnectedThread extends Thread {
	        private final BluetoothSocket mmSocket;
	        private final OutputStream mmOutStream;
			private BufferedReader mmBufReader;
			private long timeout=60000;
			private long lastDate;
			private Timer t = new Timer();
			private TimerTask tt = new TimerTask() {
				
				@Override
				public void run() {
					Log.d(TAG, "Test date : last = "+(lastDate+timeout)+" current = "+System.currentTimeMillis());
					if(lastDate+timeout<System.currentTimeMillis())
					{
						connectionLost();
						ConnectedThread.this.cancel();
						
					}
					
				}
			};
		
	        public ConnectedThread(BluetoothSocket socket) {
	            Log.d(TAG, "create ConnectedThread");
	            mmSocket = socket;
	            InputStream tmpIn = null;
	            OutputStream tmpOut = null;
	            lastDate=System.currentTimeMillis();
	            

	            // Get the BluetoothSocket input and output streams
	            try {
	                tmpIn = socket.getInputStream();
	                tmpOut = socket.getOutputStream();
	            } catch (IOException e) {
	                Log.e(TAG, "temp sockets not created", e);
	            }

	            mmOutStream = tmpOut;
	            mmBufReader=new BufferedReader(new InputStreamReader(tmpIn));
	            
	        }

	        /**
	         * Will initialize the Timer wich check the timeout of the connection.
	         * After the initialization, an infinite loop of reading is launched.
	         * If a message come from the NXT, the Activitie's handler is noticed, and the message is sent to it.
	         */
	        public void run() {
	            Log.i(TAG, "BEGIN mConnectedThread");
	            //start of timeout check
	            t.schedule(tt, 0, 2000);
	            
	            // Keep listening to the InputStream while connected
	            while (true) {
	                try {
	                    // Read from the InputStream
	                    //bytes = mmInStream.read(buffer);
	                    String s=mmBufReader.readLine();
	                 
	                    	// Send the obtained bytes to the UI Activity
	                    	mHandler.obtainMessage(MenuActivity.MESSAGE_FROM_NXT, s.length(), -1, s).sendToTarget();
	                
	                    
	                	Log.i(TAG, "Something was read : "+s);
	                } catch (IOException e) {
	                    Log.e(TAG, "disconnected", e);
	                    connectionLost();
	                    break;
	                }
	                //Whatever the exception happening, the loop should'nt be stopped, the show must go on :)
	                catch (Exception e) {
	                    Log.e(TAG, "disconnected", e);
	                    connectionLost();
	                    cancel();
	                    break;
	                }
	            }
	        }

	    
	        /**
	         * Write to the connected OutStream (The NXT) and update the lastDate (for the connection timeout).
	         * @param buffer  The bytes to write
	         */
	        public void write(byte[] buffer) {
	            try {
	            	lastDate=System.currentTimeMillis();

	                mmOutStream.write(buffer);

	                // Share the sent message back to the UI Activity
	                mHandler.obtainMessage(MenuActivity.MESSAGE_WRITE, -1, -1, buffer)
	                        .sendToTarget();
	            } catch (IOException e) {
	                Log.e(TAG, "Exception during write", e);
	                connectionLost();
                   this.cancel();
	            }
	        }
	        
	        /**
	         * 
	         * Closes the connection
	         */
	        public void cancel() {
	            try {
	                mmSocket.close();
	            } catch (IOException e) {
	                Log.e(TAG, "close() of connect socket failed", e);
	            }
	            catch(Exception e)
	            {
	            	//Nothing to say, silent close
	            }
	        }
	    }
	  
	/**
	 * Create a localbinder for the activities using the bluetooth service.
	 *
	 */
	public class LocalBinder extends Binder {
		  public BluetoothCommService getService() {
	            return BluetoothCommService.this;
	        }
	    }
	
	/**
     * Indicate that the connection failed and notify the UI Activity.
     */
    private void connectionFailed() {
        setState(STATE_NONE);

        // Send a failure message back to the Activity
        Message msg = mHandler.obtainMessage(MenuActivity.MESSAGE_TOAST);
        Bundle bundle = new Bundle();
        bundle.putString(MenuActivity.TOAST, "Unable to connect device");
        msg.setData(bundle);
        mHandler.sendMessage(msg);
    }

    /**
     * Indicate that the connection was lost and notify the UI Activity.
     */
    private void connectionLost() {
    	
    	
        setState(STATE_NONE);

        // Send a failure message back to the Activity
        Message msg = mHandler.obtainMessage(MenuActivity.MESSAGE_TOAST);
        Bundle bundle = new Bundle();
        bundle.putString(MenuActivity.TOAST, "Device connection was lost");
        msg.setData(bundle);
        mHandler.sendMessage(msg);
    }


	
	public void write(byte[] b)
	{
		// Create temporary object
        ConnectedThread r;
        // Synchronize a copy of the ConnectedThread
     
        synchronized (this) {
            if (mState != STATE_CONNECTED) return;
            r = mConnectedThread;
        }
        // Perform the write unsynchronized
        r.write(b);
	}
	
	
	/**
	 * The Service is started as STICKY
	 */
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		
		return START_STICKY;
	}

	/**
	 * This method is used to modify the new handler wich will be used to communicate with activities.
	 * @param mHandler2 the new handler
	 */
	public void setHandler(Handler mHandler2) {
		this.mHandler=mHandler2;
		
	}
	
	/**
	 * On destroy, the connection is closed
	 */
 @Override
public void onDestroy() {
	super.onDestroy();
	cancel();
}

}
