/**
 * This file was auto-generated by the Titanium Module SDK helper for Android
 * Appcelerator Titanium Mobile
 * Copyright (c) 2009-2013 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Apache Public License
 * Please see the LICENSE included with this distribution for details.
 *
 */
package com.novarum.bluetooth;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.UUID;

import org.appcelerator.kroll.KrollDict;
import org.appcelerator.kroll.KrollModule;
import org.appcelerator.kroll.annotations.Kroll;

import org.appcelerator.titanium.TiApplication;
import org.appcelerator.titanium.util.TiIntentWrapper;
import org.appcelerator.kroll.common.Log;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Message;

@Kroll.module(name="Novarumbluetooth", id="com.novarum.bluetooth")
public class NovarumbluetoothModule extends KrollModule
{

	// Standard Debugging variables
	private static final String TAG = "NovarumbluetoothModule";
	
	       BluetoothDevice  bluetoothDevice  = null;
	static BluetoothAdapter bluetoothAdapter = null;
	       KrollDict devicelist              = null;
    public static BluetoothSocket  btsocket;
    
    public static String DEFAULT_UUID = "00001101-0000-1000-8000-00805F9B34FB";
    
    private static InputStream inputStream   = null;
    private static OutputStream outputStream = null;
    public boolean isConnected               = false; 
    public dataReceiver datareceiver;
    public AcceptThread acceptthread;
    public String SERVERNAME                 = "NovarumBluetooth";
    public static NovarumbluetoothModule staticNovarumbluetoothModule;
    public boolean useService                = false;
    TiIntentWrapper BluetoothServiceIntent = null;
    
	// You can define constants with @Kroll.constant, for example:
	// @Kroll.constant public static final String EXTERNAL_NAME = value;
	
	public NovarumbluetoothModule()
	{
		super();
		staticNovarumbluetoothModule = this;
	}

	@Kroll.onAppCreate
	public static void onAppCreate(TiApplication app)
	{
		Log.d(TAG, "inside onAppCreate");
		// put module init code that needs to run when the application is created
		
		bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
		
	}
	

	// Methods
	@Kroll.method
	public String example()
	{
		Log.d(TAG, "example called");
		return "hello world";
	}
	
	
	@Kroll.method
	public void useService()
	{
		Log.d(TAG, "useService called");
		this.useService = true;
	}	
	

	@Kroll.method
	public boolean searchDevices()
	{
		Log.d(TAG, "searchDevices called");
		
		//Halilk: if not enabled, enable bluetooth
		enableBluetooth();
		
		//Get Current activity//
		TiApplication appContext = TiApplication.getInstance();
		Activity activity = appContext.getCurrentActivity();		
		
        IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        activity.registerReceiver(myReceiver, intentFilter);
        bluetoothAdapter.cancelDiscovery(); //cancel if it's already searching
        bluetoothAdapter.startDiscovery();		
	
		return true;
	}	
	
	
	@Kroll.method
	public void enableBluetooth()
	{
        if(!bluetoothAdapter.isEnabled())
        {
            bluetoothAdapter.enable();
            Log.i(TAG, "Bluetooth Enabled");
        }		
	}
	
	@Kroll.method
	public void disableBluetooth()
	{
        if(bluetoothAdapter.isEnabled())
        {
            bluetoothAdapter.disable();
            Log.i(TAG, "Bluetooth Disabled");
        }		
	}	
	
	
	@Kroll.method
	public void destroy()
	{
		Log.d(TAG, "destroy called");
		
		//Get Current activity//
		TiApplication appContext = TiApplication.getInstance();
		Activity activity = appContext.getCurrentActivity();
        
		try
		{
          activity.unregisterReceiver(myReceiver);		
		}
		catch(Exception e)
		{
			
		}
		
        //Close reader thread//       
        isConnected = false;
        
        //Close Server//
        stopServer();
        
        //Close connection//
		if (btsocket != null) 
		{
			try 
			{
				if (bluetoothAdapter != null)
					bluetoothAdapter.cancelDiscovery();
				
				btsocket.close();
			} 
			catch (Exception e) 
			{
				Log.w(TAG, "Bluetooth Socket close exception");
			}
		}        
        
        
		
		disableBluetooth();
		bluetoothAdapter = null;
		
		
	}	
	
	
	@Kroll.method
	public KrollDict getPairedDevices()
	{
		Log.d(TAG, "getPairedDevices called");
		
		Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
		
		KrollDict result = new KrollDict();
		
		// If there are paired devices
		if (pairedDevices.size() > 0) 
		{
		    // Loop through paired devices
		    for (BluetoothDevice device : pairedDevices) 
		    {
		    	//Halilk: for some devices, device name is the same so put some of mac digits
		    	String strDigitstoAdd = device.getAddress();
		    	strDigitstoAdd = strDigitstoAdd.replace(":","");
		    	result.put(device.getName()+"_"+strDigitstoAdd, device.getAddress());		    	
		    }
		}
		
		return result;
		
	}	
	

	private void devicesFound()
	{    	
    	this.fireEvent("nb_DevicesFound", devicelist);     			
	}
	
	public static void sendEvent(String eventname,KrollDict data)
	{
		staticNovarumbluetoothModule.fireEvent(eventname, data); 
	}
	
	
	public boolean pairDevice(BluetoothDevice btDevice)
	{
		
		Method createBondMethod = null;
		boolean returnValue = false;
		
		try 
		{
			createBondMethod = BluetoothDevice.class.getMethod("createBond");
			
		} 
		catch (NoSuchMethodException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		catch (SecurityException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try 
		{
			returnValue = (Boolean) createBondMethod.invoke(btDevice);
		} 
		catch (IllegalAccessException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		catch (IllegalArgumentException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		catch (InvocationTargetException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
		
		return returnValue;
	}
	
	
	public boolean socketConnect(BluetoothDevice btDevice)
	{
		try
		{
			btsocket = btDevice.createRfcommSocketToServiceRecord(UUID.fromString(DEFAULT_UUID));
			
			Method m = btDevice.getClass().getMethod("createRfcommSocket",new Class[] { int.class });
			
			btsocket = (BluetoothSocket) m.invoke(btDevice, 1);
			btsocket.connect();	

			inputStream   = btsocket.getInputStream();
			outputStream  = btsocket.getOutputStream();			
			
			isConnected = true;
			
			//open a thread for reading data//
			datareceiver = new dataReceiver();
			datareceiver.start();			
			//open a thread for reading data//
			
			//Fire an event//
			this.fireEvent("nb_onConnect",null);
			return true;
			
		}
		catch(Exception e)
		{
			postError(e.getMessage());
			return false;
		}
		
		
	}
	
	
	@Kroll.method
	public boolean connect(String devicemac)
	{
		if(devicemac == null)
		   return false;
		
		//Check if we should use the service//
		if(useService)
		{
			
			//Start Service//
			try
			{
				//Get Current activity//
				TiApplication appContext = TiApplication.getInstance();
				Activity activity = appContext.getCurrentActivity();
				
				BluetoothServiceIntent = new TiIntentWrapper(new Intent(activity,BluetoothService.class));
				BluetoothServiceIntent.getIntent().putExtra("MacAddress",devicemac);
				appContext.startService(BluetoothServiceIntent.getIntent());
				
				return true;
			}
			catch(Exception e)
			{
				Log.w(TAG,"error on creating bluetooth service: "+e.getMessage());
				return false;					
			}
			
			
		}
		else
		{		
			bluetoothDevice = bluetoothAdapter.getRemoteDevice(devicemac);
			
			if (bluetoothDevice.getBondState() == BluetoothDevice.BOND_NONE) 
			{
				if(pairDevice(bluetoothDevice))
				{
					
					return socketConnect(bluetoothDevice);
				}
				else
				{
					postError("Could not pair device");
					return false;
				}
			}
			else
			{
				return socketConnect(bluetoothDevice);				
			}
		}
	}

	
	private void PostReceivedData(String data)
	{
		if(data == null)
			return;
		
		
		KrollDict receivedict = new KrollDict();
		
		receivedict.put("data", data);
		this.fireEvent("nb_onReceiveData", receivedict);
		
		
		
	}
	
	
	
	@Kroll.method
	public boolean sendData(String data) 
	{
		if(useService)
		{
			  return BluetoothService.sendData(data);
		}
		else
		{
			if (btsocket != null && isConnected == true) 
			{
				try 
				{
					outputStream.write(data.getBytes());
					outputStream.flush();
					
					return true;
					
				} 
				catch (Exception e) 
				{
					postError(e.getMessage());
					return false;
				}
			}
			else
			{
				postError("Not connected or data is null");
				return false;
			}
		}
	}	
	
	
	@Kroll.method
	public boolean isConnected() 
	{	
		if(useService)
		  return BluetoothService.isConnected();
		else		  
		  return isConnected;
	}
	
	@Kroll.method
	public void Disconnect() 
	{	
		if(useService)
		{
			//Destroy Service//
			try
			{
				TiApplication appContext = TiApplication.getInstance();
			    appContext.stopService(BluetoothServiceIntent.getIntent());
			}
			catch(Exception e)
			{
				Log.w(TAG,"error on stopping the service: "+e.getMessage());
			}
		}
		else
		{
			if(isConnected)
			{
			    try
			    {
			    	isConnected = false;
			    	
			    	if(inputStream != null)
					   inputStream.close();
						
					if(outputStream != null)
					   outputStream.close();
						
					if(btsocket != null)
					   btsocket.close();
							
			    }
			    catch(Exception e)
			    {
			       e.printStackTrace();
			    }
			}
		}
	}	
	
	
	private void postError(String Error)
	{
		KrollDict data = new KrollDict();
		data.put("error", Error);
		this.fireEvent("nb_onError", data);
	}
	
	
    private BroadcastReceiver myReceiver = new BroadcastReceiver() 
    {
        @Override
        public void onReceive(Context context, Intent intent) 
        {
            Message msg = Message.obtain();
            String action = intent.getAction();
            
            if(BluetoothDevice.ACTION_FOUND.equals(action))
            {

            	bluetoothDevice        = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            	BluetoothClass blclass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
            	
            	int Majorclass = blclass.getMajorDeviceClass();
            	int minorclass = blclass.getDeviceClass();
            	
            	
                devicelist = new KrollDict();
                
                try
                {
                	devicelist.put("name",bluetoothDevice.getName());
                	devicelist.put("macaddress",bluetoothDevice.getAddress());
                	devicelist.put("pairedstatus",bluetoothDevice.getBondState());
                }
                catch (Exception e) 
                {
                    Log.w(TAG, "devicesFound exception: "+e.getMessage());
                    postError(e.getMessage());
                }

                devicesFound();
                
            }           
        }
    };	
	
	
    
    
	//Halilk: Bluetooth data reciever thread//
	class dataReceiver extends Thread 
	{
		public void run() 
		{

			while (isConnected) 
			{
				if (inputStream != null) 
				{
					try 
					{

						byte[] data = new byte[1024]; //read data 1kb

						int length = inputStream.read(data);
						
						
						if(length > 0)
						{
							String ReceivedData = null;
							try
							{
							   ReceivedData = new String(data,0,length,"UTF-8");
							}
							catch(Exception e)
							{
							   Log.w(TAG,"Error on creating data script: "+e.getMessage());
							}
							
							if(ReceivedData != null)
							{
							   PostReceivedData(ReceivedData); // send received data to the app
							}
						}

					} 
					catch (IOException e) 
					{
						postError(e.getMessage());
					}
				}
			}
		}
	}
	
	
	
	private void manageConnectedSocket()
	{
		try 
		{
			inputStream   = btsocket.getInputStream();
			outputStream  = btsocket.getOutputStream();
		} 
		catch (IOException e) 
		{
			postError(e.getMessage());
			
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		isConnected = true;
				
		//open a thread for reading data//
		try
		{
			datareceiver = new dataReceiver();
			datareceiver.start();			
		}
		catch(Exception e)
		{
			postError(e.getMessage());
		}
		//open a thread for reading data//
		
		this.fireEvent("nb_onConnect",null);
		
	}
	
	
	
	@Kroll.method
	public void startServer() 
	{	
		try
		{
			acceptthread = new AcceptThread();
			acceptthread.start();
			this.fireEvent("nb_onServerStarted",null);
		}
		catch(Exception e)
		{
			postError(e.getMessage());
		}
	}
	
	
	@Kroll.method
	public void stopServer() 
	{
		acceptthread.cancel();
	}	
	
	
	@Kroll.method
	public void setServerName(String name) 
	{	
		this.SERVERNAME = name;
	}	
	

	@Kroll.method
	public void setUUID(String uuid) 
	{	
		this.DEFAULT_UUID = uuid;
	}	
	
	
	@Kroll.method
	public void makeDiscoverable() 
	{	
		if(bluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)
		{		
			TiApplication appContext = TiApplication.getInstance();
			Activity activity = appContext.getCurrentActivity();
			
			Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
		    discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
		    activity.startActivity(discoverableIntent);
		}
	}
	
	
	public void discoverabilityResult(int Result)
	{
		KrollDict data = new KrollDict();
		
		data.put("result",Integer.toString(Result));
		
		Log.w(TAG,"NovarumBluetooth Discoverability Result" + Result);
		
		fireEvent("nb_onDiscoverabilityResult", data);
	}
	
	
	public void onActivityResult(int requestCode, int resultCode, Intent data) 
	{

		switch (resultCode) 
		{
		   case Activity.RESULT_OK:
		      discoverabilityResult(1);
		   break;
		   
		   case Activity.RESULT_CANCELED:
			  discoverabilityResult(0);
		   break;		   
		   
		   default:
			  discoverabilityResult(resultCode);
		   break;
		   
		}
	}
	
	
	
	
	
	private class AcceptThread extends Thread 
	{
	    private final BluetoothServerSocket mmServerSocket;
	 
	    public AcceptThread() 
	    {
	        // Use a temporary object that is later assigned to mmServerSocket,
	        // because mmServerSocket is final
	        BluetoothServerSocket tmp = null;
	        try 
	        {
	            // MY_UUID is the app's UUID string, also used by the client code
	            tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(SERVERNAME, UUID.fromString(DEFAULT_UUID));
	        } 
	        catch (IOException e) 
	        { 
	        	postError(e.getMessage());
	        }
	        
	        mmServerSocket = tmp;
	    }
	 
	    public void run() 
	    {
	        btsocket = null;
	        
	        // Keep listening until exception occurs or a socket is returned
	        while (true) 
	        {
	            try 
	            {
	            	btsocket = mmServerSocket.accept();
	            } 
	            catch (IOException e) 
	            {
	            	postError(e.getMessage());
	            }
	            
	            // If a connection was accepted
	            if (btsocket != null) 
	            {
	                // Do work to manage the connection (in a separate thread)
	                manageConnectedSocket();
	                
	                try 
	                {
						mmServerSocket.close();
					} 
	                catch (IOException e) 
	                {
	                	postError(e.getMessage());
	                	// TODO Auto-generated catch block
						e.printStackTrace();
					}
	                
	                break;
	            }
	        }
	    }
	 
	    /** Will cancel the listening socket, and cause the thread to finish */
	    public void cancel() 
	    {
	        try 
	        {
	            mmServerSocket.close();
	        } 
	        catch (IOException e) 
	        { 
	        	postError(e.getMessage());
	        }
	    }
	}	
	

}