/*
   Copyright 2012 Wolfgang Koller - http://www.gofg.at/

   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 org.apache.cordova.btprinter;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginResult;
import org.apache.cordova.CordovaInterface;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.Set;

/**
 * PhoneGap Plugin for Serial Communication over Bluetooth
 */
public class BluetoothPrinter extends CordovaPlugin {

    // actions
    private static final String LIST = "list";
    private static final String CONNECT = "connect";
    private static final String CONNECT_INSECURE = "connectInsecure";
    private static final String DISCONNECT = "disconnect";
    private static final String WRITE = "write";
    private static final String PRINT = "print";
    private static final String AVAILABLE  = "available";
    private static final String READ  = "read";
    private static final String READ_UNTIL  = "readUntil";
    private static final String SUBSCRIBE = "subscribe";
    private static final String UNSUBSCRIBE  = "unsubscribe";
    private static final String IS_ENABLED = "enable";
    private static final String IS_CONNECTED  = "isConnected";
    private static final String CLEAR  = "clear";

    // callbacks
    private CallbackContext connectCallback;
    private CallbackContext dataAvailableCallback;

    private BluetoothAdapter bluetoothAdapter;
    private BluetoothPrinterService bluetoothPrinterService;

    // Debugging
    private static final String TAG = "BluetoothPrinter";
    private static final boolean D = true;

    // Message types sent from the BluetoothSerialService Handler
    public static final int MESSAGE_STATE_CHANGE = 1;
    public static final int MESSAGE_READ = 2;
    public static final int MESSAGE_WRITE = 3;
    public static final int MESSAGE_DEVICE_NAME = 4;
    public static final int MESSAGE_TOAST = 5;

    // Key names received from the BluetoothChatService Handler
    public static final String DEVICE_NAME = "device_name";
    public static final String TOAST = "toast";

    StringBuffer buffer = new StringBuffer();
    private String delimiter;

    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        if (bluetoothAdapter == null) {
            bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        }

        if (bluetoothPrinterService == null) {
        	bluetoothPrinterService = new BluetoothPrinterService(mHandler);
        }

        boolean validAction = true;
        
        if (action.equals(LIST)) {

            listBondedDevices(callbackContext);

        } else if (action.equals(CONNECT)) {

            boolean secure = true;
            connect(args, secure, callbackContext);

        } else if (action.equals(CONNECT_INSECURE)) {

            // see Android docs about Insecure RFCOMM http://goo.gl/1mFjZY
            boolean secure = false;
            connect(args, false, callbackContext);

        } else if (action.equals(DISCONNECT)) {

            connectCallback = null;
            bluetoothPrinterService.stop();
            callbackContext.success();

        } else if (action.equals(WRITE)) {

            String data = args.getString(0);
            bluetoothPrinterService.write(data.getBytes());
            callbackContext.success();

        } else if (action.equals(PRINT)) {
        	try {
        		String data = args.getString(0);// + 0x1D + 0x56 + 0x00;
        		Log.i(TAG, "[print]"+data);
        		bluetoothPrinterService.write(data.getBytes("gbk"));
        		callbackContext.success();
        	}
        	catch( Exception e ) 
			{
				callbackContext.error(e.getMessage());
			}
        }else if (action.equals(AVAILABLE)) {

            callbackContext.success(available());

        } else if (action.equals(READ)) {

            callbackContext.success(read());

        } else if (action.equals(READ_UNTIL)) {

            String interesting = args.getString(0);
            callbackContext.success(readUntil(interesting));

        } else if (action.equals(SUBSCRIBE)) {

            delimiter = args.getString(0);
            dataAvailableCallback = callbackContext;

            PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT);
            result.setKeepCallback(true);
            callbackContext.sendPluginResult(result);

        } else if (action.equals(UNSUBSCRIBE)) {

            delimiter = null;
            dataAvailableCallback = null;

            callbackContext.success();

        } else if (action.equals(IS_ENABLED)) {

            if (bluetoothAdapter.isEnabled()) {
                callbackContext.success();                
            } else {
            	bluetoothAdapter.enable();
            	callbackContext.success();
            }            

        } else if (action.equals(IS_CONNECTED)) {
            
            if (bluetoothPrinterService.getState() == BluetoothPrinterService.STATE_CONNECTED) {
                callbackContext.success();                
            } else {
                callbackContext.error("Not connected.");
            }

        } else if (action.equals(CLEAR)) {

            buffer.setLength(0);
            callbackContext.success();

        } else {

            validAction = false;

        }

        return validAction;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (bluetoothPrinterService != null) {
        	bluetoothPrinterService.stop();
        }
    }

    private void listBondedDevices(CallbackContext callbackContext) throws JSONException {
        JSONArray deviceList = new JSONArray();
        Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices();

        for (BluetoothDevice device : bondedDevices) {
            JSONObject json = new JSONObject();
            json.put("name", device.getName());
            json.put("address", device.getAddress());
            if (device.getBluetoothClass() != null) {
                json.put("class", device.getBluetoothClass().getDeviceClass());
            }
            deviceList.put(json);
        }
        callbackContext.success(deviceList);
    }

    private void connect(JSONArray args, boolean secure, CallbackContext callbackContext) throws JSONException {
        String macAddress = args.getString(0);
        try {
	        BluetoothDevice device = bluetoothAdapter.getRemoteDevice(macAddress);
	
	        if (device != null) {
	            connectCallback = callbackContext;
	            bluetoothPrinterService.connect(device, secure);
	
	            PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT);
	            result.setKeepCallback(true);
	            callbackContext.sendPluginResult(result);
	
	        } else {
	            callbackContext.error("Could not connect to " + macAddress);
	        }
        }
        catch(Exception e)
        {
        	callbackContext.error(e.getMessage());
        }
    }

    // The Handler that gets information back from the BluetoothSerialService
    // Original code used handler for the because it was talking to the UI.
    // Consider replacing with normal callbacks
    private final Handler mHandler = new Handler() {

         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MESSAGE_READ:
                    buffer.append((String)msg.obj);

                    if (dataAvailableCallback != null) {
                        sendDataToSubscriber();
                    }
                    break;
                 case MESSAGE_STATE_CHANGE:

                    if(D) Log.i(TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1);
                    switch (msg.arg1) {
                        case BluetoothPrinterService.STATE_CONNECTED:
                            Log.i(TAG, "BluetoothPrinterService.STATE_CONNECTED");
                            notifyConnectionSuccess();
                            break;
                        case BluetoothPrinterService.STATE_CONNECTING:
                            Log.i(TAG, "BluetoothPrinterService.STATE_CONNECTING");
                            break;
                        case BluetoothPrinterService.STATE_LISTEN:
                            Log.i(TAG, "BluetoothPrinterService.STATE_LISTEN");
                            break;
                        case BluetoothPrinterService.STATE_NONE:
                            Log.i(TAG, "BluetoothPrinterService.STATE_NONE");
                            break;
                        case BluetoothPrinterService.STATE_DISCONNECTED:
                        	notifyConnectionLost("connection lost");
                        	break;
                    }
                    break;
                case MESSAGE_WRITE:
                    //  byte[] writeBuf = (byte[]) msg.obj;
                    //  String writeMessage = new String(writeBuf);
                    //  Log.i(TAG, "Wrote: " + writeMessage);
                    break;
                case MESSAGE_DEVICE_NAME:
                    Log.i(TAG, msg.getData().getString(DEVICE_NAME));
                    break;
                case MESSAGE_TOAST:
                    String message = msg.getData().getString(TOAST);
                    notifyConnectionLost(message);
                    break;
             }
         }
    };

    private void notifyConnectionLost(String error) {
        if (connectCallback != null) {
            connectCallback.error(error);
            connectCallback = null;
        }
    }

    private void notifyConnectionSuccess() {
        if (connectCallback != null) {
            PluginResult result = new PluginResult(PluginResult.Status.OK);
            result.setKeepCallback(true);
            connectCallback.sendPluginResult(result);
        }
    }

    private void sendDataToSubscriber() {
        String data = readUntil(delimiter);
        if (data != null && data.length() > 0) {
            PluginResult result = new PluginResult(PluginResult.Status.OK, data);
            result.setKeepCallback(true);
            dataAvailableCallback.sendPluginResult(result);
        }
    }

    private int available() {
        return buffer.length();
    }

    private String read() {
        int length = buffer.length();
        String data = buffer.substring(0, length);
        buffer.delete(0, length);
        return data;
    }

    private String readUntil(String c) {
        String data = "";
        int index = buffer.indexOf(c, 0);
        if (index > -1) {
            data = buffer.substring(0, index + c.length());
            buffer.delete(0, index + c.length());
        }
        return data;
    }
}