package kr.co.sevencore.blefotalib;

import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.widget.SimpleExpandableListAdapter;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;

import kr.co.sevencore.blefotalib.BflCodeList.UploadCode;
import kr.co.sevencore.blefotalib.BflCodeList.ServiceIdxCode;
import kr.co.sevencore.blefotalib.BflCodeList.CharacteristicFotaIdxCode;
import kr.co.sevencore.blefotalib.BflCodeList.CharacteristicDeviceInfoIdxCode;

/**
 * BflFwUploader.java
 * BLE FOTA Library Firmware Upload.
 *
 * 2015 SEVENCORE Co., Ltd.
 *
 * @author Jungwoo Park
 * @version 1.0.0
 * @since 2015-04-08
 * @see kr.co.sevencore.blefotalib.BflFwUploadService
 * @see kr.co.sevencore.blefotalib.IBflFwUploadSvc
 * TODO.. Apply flag value - 00: The BLE device is rebooted by the old firmware, 01: The BLE device is rebooted by the new firmware.
 */
public class BflFwUploader {
    private final static String BLE_FOTA_TAG = BflFwUploader.class.getSimpleName();

    public IBflFwUploadSvc mBflUploadBinder;    // Firmware data upload service AIDL.
    private Intent mUploadServiceIntent;
    private String mAddress;                     // The target device MAC address.

    private Context mContext;

    private static String sFirmwareCurrentVersion;       // Current target device(BLE server) firmware version.
    private static String sFirmwareNewVersion;           // If another firmware data exists at the target device(BLE server), return firmware version of the target device,
    private static String sFirmwareVersion;              // Selected firmware version at the smart device(BLE client).

    private static String sFilePath;                     // Selected firmware data file path.
    private static int sLeftConnCnt = 0;                // Left firmware data count.
    private static int sSequenceNumber = -1;            // Sequence number starts at 0.

    private static String sFirmwareDataStatus;           // Firmware data integrity status.
    private static String sFirmwareStatus;               // Firmware software status.

    private static byte sFirmwareUpgradeTypeFlag = 0;   // Flag 0 : Normal upgrade | Flag 1 : Forced upgrade
    private static byte sResetFlag = 1;                 // Flag 0: Not reset | Flag 1: Reset

    private static String sManufacturerName;             // Device manufacturer name.
    private static String sModelNumber;                  // Device model number.
    private static String sSerialNumber;                 // Device serial number.

    private static String sExtraData;                    // Extra data information by read or write characteristic.

    private static boolean sInitAutoProgressFlag = false; // Initialization for FOTA automation flag.
    private static boolean sAutoProgressFlag = false;     // Firmware upgrade automation flag.

    private final String NORMAL = "0";                // Firmware data check & status normal status.
    private final String VALIDATE = "1";             // Firmware data validate status.
    private final String INVALIDATE = "2";           // Firmware data invalidate status.
    private final String SUCCESSFUL = "1";           // Firmware status successful status.
    private final String ABNORMAL_FINISH = "2";     // Firmware status abnormal finish status.

    private OnUploadSvcInit mUploadSvcInitCallback;                 // Service initialization result of the firmware upload callback.
    private OnConnectionState mConnectionCallback;                   // BLE connection status callback.
    private OnErrorStateListener mErrorStateCallback;                // Error state for connection callback.
    private OnUpdateGattServiceListener mUpdateGattServiceCallback; // GATT service list adapter callback.
    private OnDeviceInfoListener mDeviceInfoCallback;                // Device and firmware status related information in doing FOTA callback.


    public BflFwUploader() {}

    public BflFwUploader(Context context) {
        mContext = context;
    }

    /**
     * OnUploadSvcInit interface is used to get result of firmware upload service initialization.
     */
    public interface OnUploadSvcInit {
        /**
         * Initialization using BLE of the smart device.
         *
         * @param initResult true: All of BLE related feature is normal state.
         *                   false: Unable to initialize BLE relate feature.
         */
        void onUploadSvcInit(boolean initResult);
    }

    /**
     * OnConnectionState interface is used to get state of connection.
     * If a main application needs to get state of BLE connection state,
     * use onConnectionState method.
     */
    public interface OnConnectionState {
        /**
         * Get BLE connection state.
         *
         * @param state true: connected
         *              false: disconnected.
         */
        void onConnectionState(boolean state);
    }

    /**
     * OnErrorStateListener interface is used to notify error state.
     * If a main application needs to get the error state of Bluetooth GATT or device information to used for connection,
     * use onErrorStateListener.
     */
    public interface OnErrorStateListener {
        /**
         * Get error state.
         *
         * @param state true: Error state.
         *              false: Normal state.
         */
        void onErrorStateListener(boolean state);
    }

    /**
     * OnUpdateGattServiceListener interface is used to get GATT service adapter.
     * If a main application needs to show service list by expandable list adapter,
     * use onUpdateGattServiceListener method.
     */
    public interface OnUpdateGattServiceListener {
        /**
         * Get GATT service list adapter of the target device.
         *
         * @param gattServiceAdapter includes GATT services lists.
         */
        void onUpdateGattServiceListener(SimpleExpandableListAdapter gattServiceAdapter);
    }

    /**
     * OnDeviceInfoListener interface is used to get firmware upgrade progress related data of the target device.
     * If a main application needs a firmware upgrade progress related information,
     * use onDeviceInfoListener method.
     */
    public interface OnDeviceInfoListener {
        /**
         * Used to get string information data of firmware upgrade progress.
         *
         * @param code is a progress identifier of firmware upgrade progress.
         * @param data is the information related each progress state.
         */
        void onDeviceInfoListener(String code, String data);

        /**
         * Used to get integer value of sequence number about firmware upgrade.
         *
         * @param code is a progress identifier of firmware upgrade progress.
         * @param data is the sequence number value.
         */
        void onDataSequenceNumberListener(String code, int data);
    }

    /**
     * Save a callback object to mUploadSvcInitCallback.
     *
     * @see kr.co.sevencore.blefotalib.BflFwUploader.OnUploadSvcInit
     */
    public void setOnUploadSvcInit(OnUploadSvcInit callback) {
        mUploadSvcInitCallback = callback;
    }

    /**
     * Save a callback object to mConnectionCallback.
     *
     * @see kr.co.sevencore.blefotalib.BflFwUploader.OnConnectionState
     */
    public void setOnConnectionState(OnConnectionState callback) {
        mConnectionCallback = callback;
    }

    /**
     * Save a callback object to mErrorStateCallback.
     *
     * @see kr.co.sevencore.blefotalib.BflFwUploader.OnErrorStateListener
     */
    public void setOnErrorStateListener(OnErrorStateListener callback) {
        mErrorStateCallback = callback;
    }

    /**
     * Save a callback object to mUpdateGattServiceCallback.
     *
     * @see kr.co.sevencore.blefotalib.BflFwUploader.OnUpdateGattServiceListener
     */
    public void setOnUpdateGattServiceListener(OnUpdateGattServiceListener callback) {
        mUpdateGattServiceCallback = callback;
    }

    /**
     * Save a callback object to mDeviceInfoCallback.
     *
     * @see kr.co.sevencore.blefotalib.BflFwUploader.OnDeviceInfoListener
     */
    public void setOnDeviceInfoListener(OnDeviceInfoListener callback) {
        mDeviceInfoCallback = callback;
    }

    /**
     * Get the new firmware version information.
     *
     * @return The new firmware version information.
     */
    public String getFirmwareVersion() {
        return sFirmwareVersion;
    }

    /**
     * Get the file path of the firmware data.
     *
     * @return The path of firmware data location.
     */
    public String getFilePath() {
        return sFilePath;
    }

    /**
     * Get the firmware upgrade type.
     *
     * @return The firmware upgrade type flag.
     */
    public byte getFirmwareUpgradeType() {
        return sFirmwareUpgradeTypeFlag;
    }

    /**
     * Set the new firmware version.
     *
     * @param version is extracted from the file name to be a new firmware version.
     */
    public void setFirmwareVersion(String version) {
        sFirmwareVersion = version;
    }

    /**
     * Set the file path of the firmware data.
     *
     * @param path is the location of the firmware data.
     */
    public void setFilePath(String path) {
        sFilePath = path;
    }

    /**
     * Set the firmware upgrade type.
     *
     * @param type is firmware upgrade type.
     */
    public void setFirmwareUpgradeType(byte type) {
        sFirmwareUpgradeTypeFlag = type;
    }

    /**
     * Create an object to implement a service connection interface.
     * The firmware upload service updates the firmware of the BLE device.
     *
     * @see kr.co.sevencore.blefotalib.IBflFwUploadSvc
     * @see kr.co.sevencore.blefotalib.BflFwUploadService
     */
    private final ServiceConnection mBflUploadSvcConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBflUploadBinder = IBflFwUploadSvc.Stub.asInterface(service);
            boolean initResult = true;

            try {
                if (!mBflUploadBinder.initUploader()) {
                    initResult = false;
                    Log.e(BLE_FOTA_TAG, "Unable to initialize Bluetooth.");
                }

                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if (mUploadSvcInitCallback != null) {
                    mUploadSvcInitCallback.onUploadSvcInit(initResult);
                }

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if(mAddress != null) {
                    mBflUploadBinder.connect(mAddress);
                } else {
                    Log.e(BLE_FOTA_TAG, "Connection creation failed because MAC address value is NULL.");
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBflUploadBinder = null;
        }
    };

    /**
     * Broadcast receiver of firmware upload service.
     * Handle various events fired by the Service.
     * ACTION_GATT_CONNECTED: Connected to a GATT server.
     * ACTION_GATT_DISCONNECTED: Disconnected from a GATT server.
     * ACTION_GATT_SERVICES_DISCOVERED: GATT services Discovered.
     * ACTION_GATT_DATA_AVAILABLE: Update GATT services & characteristics list to be used to do FOTA.
     * ACTION_DATA_AVAILABLE: Received data from the device. A result of read or notification operations.
     * ACTION_DATA_WRITABLE: Transmitting data from client device. A result of write operation.
     *
     * @see kr.co.sevencore.blefotalib.BflFwUploadService
     */
    private final BroadcastReceiver mBflUploadBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();

            if (action != null) {
                if (BflFwUploadService.ERROR_LOST_GATT.equals(action)) {

                    if (mErrorStateCallback != null) {
                        mErrorStateCallback.onErrorStateListener(true);
                    }
                } else if (BflFwUploadService.ERROR_LOST_DEVICE_INFORMATION.equals(action)) {

                    if (mErrorStateCallback != null) {
                        mErrorStateCallback.onErrorStateListener(true);
                    }
                } else if (BflFwUploadService.ACTION_GATT_CONNECTED.equals(action)) {
                    Log.i(BLE_FOTA_TAG, "The device is connected.");

                    // Update connection state by the callback.
                    if (mConnectionCallback != null) {
                        mConnectionCallback.onConnectionState(true);
                    }
                } else if (BflFwUploadService.ACTION_GATT_DISCONNECTED.equals(action)) {
                    Log.i(BLE_FOTA_TAG, "The device is disconnected.");

                    initializeValue();

                    // Update connection state by the callback.
                    if (mConnectionCallback != null) {
                        mConnectionCallback.onConnectionState(false);
                    }
                } else if (BflFwUploadService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
                    try {
                        // Update GATT services to do FOTA.
                        mBflUploadBinder.updateGatt();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                } else if (BflFwUploadService.ACTION_GATT_DATA_AVAILABLE.equals(action)) {
                    // Show all the supported services and characteristics on the user interface.
                    if (mUpdateGattServiceCallback != null) {
                        mUpdateGattServiceCallback.onUpdateGattServiceListener(
                                updateGattServicesAdapter(
                                        (ArrayList<HashMap<String, String>>) intent.getExtras().
                                                getSerializable(BflFwUploadService.GATT_SERVICE_DATA_AVAILABLE),
                                        (ArrayList<ArrayList<HashMap<String, String>>>) intent.getExtras().
                                                getSerializable(BflFwUploadService.GATT_CHARACTERISTIC_DATA_AVAILABLE)
                                )
                        );
                    }

                    if (sInitAutoProgressFlag) {
                        // Firmware version characteristic read property execution.
                        executeReadFirmwareCurrentVersion();
                    }

                } else if (BflFwUploadService.ACTION_FIRMWARE_CURRENT_VERSION_AVAILABLE.equals(action)) {
                    sFirmwareCurrentVersion = intent.getStringExtra(BflFwUploadService.EXTRA_DATA);

                    // Compare the device firmware version and the server firmware version.
                    Intent versionInfoIntent = new Intent(BflFwVerChecker.ACTION_TARGET_VERSION);
                    versionInfoIntent.putExtra(BflFwVerChecker.VERSION_INFO, sFirmwareCurrentVersion);
                    versionInfoIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
                    context.sendBroadcast(versionInfoIntent);

                    if (sInitAutoProgressFlag) {
                        // Firmware new version characteristic read property execution.
                        executeReadFirmwareNewVersion();
                    }

                    if (mDeviceInfoCallback != null) {
                        mDeviceInfoCallback.onDeviceInfoListener(
                                UploadCode.DEVICE_FIRMWARE_CURRENT_VERSION.getCode(),
                                sFirmwareCurrentVersion
                        );
                    }
                    Log.d(BLE_FOTA_TAG, "Current firmware version: " + sFirmwareCurrentVersion);

                } else if (BflFwUploadService.ACTION_FIRMWARE_NEW_VERSION_AVAILABLE.equals(action)) {
                    sFirmwareNewVersion = intent.getStringExtra(BflFwUploadService.EXTRA_DATA);

                    final String defaultVersion = "00-00-00";

                    if (sInitAutoProgressFlag) {
                        // Manufacturer name characteristic read property execution.
                        executeReadManufacturerName();
                    }

                    // If you want to manage firmware version information
                    // between a new firmware data which is going to be transmitted
                    // and a existing new firmware data which exists on the device,
                    // use below conditional statement.
                    if (sFirmwareNewVersion.equals(defaultVersion)) {
                        if (mDeviceInfoCallback != null) {
                            mDeviceInfoCallback.onDeviceInfoListener(
                                    UploadCode.DEVICE_FIRMWARE_NEW_VERSION_EMPTINESS.getCode(),
                                    sFirmwareNewVersion
                            );
                        }
                    } else {
                        if (mDeviceInfoCallback != null) {
                            mDeviceInfoCallback.onDeviceInfoListener(
                                    UploadCode.DEVICE_FIRMWARE_NEW_VERSION_EXISTENCE.getCode(),
                                    sFirmwareNewVersion
                            );
                        }
                    }
                    Log.d(BLE_FOTA_TAG, "New firmware version: " + sFirmwareNewVersion);

                } else if (BflFwUploadService.ACTION_SEQUENCE_NUMBER_AVAILABLE.equals(action)) {
                    if (sAutoProgressFlag && (intent.getStringExtra(BflFwUploadService.EXTRA_DATA) != null)) {
                        sSequenceNumber = Integer.parseInt(intent.getStringExtra(BflFwUploadService.EXTRA_DATA));

                        // Check the existing file size to resume transmitting the firmware data
                        // or checking firmware data integrity.
                        if (!checkSequence(sFilePath, sSequenceNumber)) {
                            try {
                                mBflUploadBinder.executeWriteFirmwareData(
                                        ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                                        CharacteristicFotaIdxCode.CHARACTERISTIC_FIRMWARE_DATA.getCode(),
                                        sFilePath, sSequenceNumber);
                            } catch (RemoteException e) {
                                e.printStackTrace();
                            }
                        } else {
                            try {
                                mBflUploadBinder.executeWriteChecksumData(
                                        ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                                        CharacteristicFotaIdxCode.CHARACTERISTIC_CHECKSUM_DATA.getCode(),
                                        sFilePath);
                            } catch (RemoteException e) {
                                e.printStackTrace();
                            }
                        }

                        if (mDeviceInfoCallback != null) {
                            mDeviceInfoCallback.onDataSequenceNumberListener(
                                    UploadCode.DEVICE_FIRMWARE_AVAILABLE_SEQUENCE_NUMBER.getCode(),
                                    sSequenceNumber
                            );
                        }
                    }
                    Log.d(BLE_FOTA_TAG, "Data sequence number of the device: " + sSequenceNumber);

                } else if (BflFwUploadService.ACTION_FIRMWARE_DATA_CHECK_AVAILABLE.equals(action)) {
                    if (sAutoProgressFlag && (intent.getStringExtra(BflFwUploadService.EXTRA_DATA) != null)) {
                        sFirmwareDataStatus = intent.getStringExtra(BflFwUploadService.EXTRA_DATA);

                        // Firmware data check flag value - NORMAL: 0, VALIDATE: 1, INVALIDATE: 2
                        switch (sFirmwareDataStatus) {
                            case NORMAL:
                                if (mDeviceInfoCallback != null) {
                                    mDeviceInfoCallback.onDeviceInfoListener(
                                            UploadCode.DEVICE_FIRMWARE_DATA_STATUS_NORMAL.getCode(),
                                            sFirmwareDataStatus
                                    );
                                }
                                Log.i(BLE_FOTA_TAG, "Firmware data has not been verified yet.");
                                break;

                            case VALIDATE:
                                try {
                                    mBflUploadBinder.executeWriteFirmwareUpgradeType(
                                            ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                                            CharacteristicFotaIdxCode.CHARACTERISTIC_FIRMWARE_UPGRADE_TYPE.getCode(),
                                            sFirmwareUpgradeTypeFlag
                                    );
                                } catch (RemoteException e) {
                                    e.printStackTrace();
                                }

                                if (mDeviceInfoCallback != null) {
                                    mDeviceInfoCallback.onDeviceInfoListener(
                                            UploadCode.DEVICE_FIRMWARE_DATA_STATUS_VALID.getCode(),
                                            sFirmwareDataStatus
                                    );
                                }
                                Log.i(BLE_FOTA_TAG, "Firmware data validated.");
                                break;

                            case INVALIDATE:
                                sAutoProgressFlag = false;

                                if (mDeviceInfoCallback != null) {
                                    mDeviceInfoCallback.onDeviceInfoListener(
                                            UploadCode.DEVICE_FIRMWARE_DATA_STATUS_INVALID.getCode(),
                                            sFirmwareDataStatus
                                    );
                                }
                                Log.i(BLE_FOTA_TAG, "Firmware data invalidated");
                                break;
                        }
                    }
                } else if (BflFwUploadService.ACTION_FIRMWARE_STATUS_AVAILABLE.equals(action)) {
                    if (sAutoProgressFlag && (intent.getStringExtra(BflFwUploadService.EXTRA_DATA) != null)) {
                        sFirmwareStatus = intent.getStringExtra(BflFwUploadService.EXTRA_DATA);

                        // Firmware upgrade status flag value - NORMAL: 0, SUCCESSFUL: 1, ABNORMAL FINISH: 2
                        switch (sFirmwareStatus) {
                            case NORMAL:
                                if (mDeviceInfoCallback != null) {
                                    mDeviceInfoCallback.onDeviceInfoListener(
                                            UploadCode.DEVICE_FIRMWARE_STATUS_NORMAL.getCode(),
                                            sFirmwareStatus
                                    );
                                }
                                Log.i(BLE_FOTA_TAG, "Upgrade is not over yet.");
                                break;

                            case SUCCESSFUL:
                                try {
                                    mBflUploadBinder.executeWriteReset(
                                            ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                                            CharacteristicFotaIdxCode.CHARACTERISTIC_RESET.getCode(),
                                            sResetFlag
                                    );
                                    Log.i(BLE_FOTA_TAG, "Target device reset now.");
                                } catch (RemoteException e) {
                                    e.printStackTrace();
                                }

                                if (mDeviceInfoCallback != null) {
                                    mDeviceInfoCallback.onDeviceInfoListener(
                                            UploadCode.DEVICE_FIRMWARE_STATUS_SUCCESSFUL_FINISH.getCode(),
                                            sFirmwareStatus
                                    );
                                }
                                Log.i(BLE_FOTA_TAG, "Firmware upgrade finished successfully.");
                                break;

                            case ABNORMAL_FINISH:
                                sAutoProgressFlag = false;

                                if (mDeviceInfoCallback != null) {
                                    mDeviceInfoCallback.onDeviceInfoListener(
                                            UploadCode.DEVICE_FIRMWARE_STATUS_ABNORMAL_FINISH.getCode(),
                                            sFirmwareStatus
                                    );
                                }
                                Log.i(BLE_FOTA_TAG, "Firmware upgrade finished abnormally.");
                                break;
                        }
                    }
                } else if (BflFwUploadService.ACTION_MANUFACTURER_NAME_AVAILABLE.equals(action)) {
                    sManufacturerName = intent.getStringExtra(BflFwUploadService.EXTRA_DATA);

                    if (sInitAutoProgressFlag) {
                        try {
                            Thread.sleep(700);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // Model number characteristic read property execution.
                        executeReadModelNumber();
                    }

                    if (mDeviceInfoCallback != null) {
                        mDeviceInfoCallback.onDeviceInfoListener(
                                UploadCode.DEVICE_INFORMATION_MANUFACTURER_NAME.getCode(),
                                sManufacturerName
                        );
                    }
                } else if (BflFwUploadService.ACTION_MODEL_NUMBER_AVAILABLE.equals(action)) {
                    sModelNumber = intent.getStringExtra(BflFwUploadService.EXTRA_DATA);

                    if (sInitAutoProgressFlag) {
                        try {
                            Thread.sleep(700);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // Serial number characteristic read property execution.
                        executeReadSerialNumber();
                    }

                    if (mDeviceInfoCallback != null) {
                        mDeviceInfoCallback.onDeviceInfoListener(
                                UploadCode.DEVICE_INFORMATION_MODEL_NUMBER.getCode(),
                                sModelNumber
                        );
                    }
                } else if (BflFwUploadService.ACTION_SERIAL_NUMBER_AVAILABLE.equals(action)) {
                    sSerialNumber = intent.getStringExtra(BflFwUploadService.EXTRA_DATA);

                    if (mDeviceInfoCallback != null) {
                        mDeviceInfoCallback.onDeviceInfoListener(
                                UploadCode.DEVICE_INFORMATION_SERIAL_NUMBER.getCode(),
                                sSerialNumber
                        );
                    }
                } else if (BflFwUploadService.ACTION_DATA_AVAILABLE.equals(action)) {
                    sExtraData = intent.getStringExtra(BflFwUploadService.EXTRA_DATA);

                    if (mDeviceInfoCallback != null) {
                        mDeviceInfoCallback.onDeviceInfoListener(
                                UploadCode.DEVICE_EXTRA_DATA.getCode(), sExtraData
                        );
                    }
                    Log.d(BLE_FOTA_TAG, "Read extra data: " + sExtraData);

                } else if (BflFwUploadService.ACTION_FIRMWARE_NEW_VERSION_WRITABLE.equals(action)) {
                    if (sAutoProgressFlag && intent.getStringExtra(BflFwUploadService.EXTRA_DATA) != null) {
                        try {
                            mBflUploadBinder.executeWriteFirmwareData(
                                    ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                                    CharacteristicFotaIdxCode.CHARACTERISTIC_FIRMWARE_DATA.getCode(),
                                    sFilePath, sSequenceNumber);
                            Log.d(BLE_FOTA_TAG, "Execute firmware upgrade. Starting firmware data transmission.");
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    Log.d(BLE_FOTA_TAG, "Transmit a new version information to the target device.");

                } else if (BflFwUploadService.ACTION_FIRMWARE_DATA_WRITABLE.equals(action)) {
                    sLeftConnCnt = Integer.parseInt(intent.getStringExtra(BflFwUploadService.EXTRA_DATA));

                    if (sAutoProgressFlag && (sLeftConnCnt == 0)) {
                        try {
                            mBflUploadBinder.executeWriteChecksumData(
                                    ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                                    CharacteristicFotaIdxCode.CHARACTERISTIC_CHECKSUM_DATA.getCode(),
                                    sFilePath);
                            Log.d(BLE_FOTA_TAG, "Generate & transmit checksum data.");
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }

                    if (mDeviceInfoCallback != null) {
                        mDeviceInfoCallback.onDataSequenceNumberListener(
                                UploadCode.DEVICE_FIRMWARE_DATA_LEFT_COUNT.getCode(),
                                sLeftConnCnt
                        );
                    }
                    Log.d(BLE_FOTA_TAG, "Left data count: " + sLeftConnCnt);

                } else if (BflFwUploadService.ACTION_SEQUENCE_NUMBER_WRITABLE.equals(action)) {
                    int writableSequenceNumber = Integer.parseInt(intent.getStringExtra(BflFwUploadService.EXTRA_DATA));

                    if (mDeviceInfoCallback != null) {
                        mDeviceInfoCallback.onDataSequenceNumberListener(
                                UploadCode.DEVICE_FIRMWARE_WRITABLE_SEQUENCE_NUMBER.getCode(),
                                writableSequenceNumber
                        );
                    }
                    Log.i(BLE_FOTA_TAG, "Sequence number is " + writableSequenceNumber);

                } else if (BflFwUploadService.ACTION_CHECKSUM_DATA_WRITABLE.equals(action)) {
                    if (sAutoProgressFlag && (intent.getStringExtra(BflFwUploadService.EXTRA_DATA) != null)) {
                        executeReadFirmwareDataCheck();
                    }
                    Log.d(BLE_FOTA_TAG, "Transmitting checksum data.");

                } else if (BflFwUploadService.ACTION_FIRMWARE_UPGRADE_TYPE_WRITABLE.equals(action)) {
                    if (sAutoProgressFlag && (intent.getStringExtra(BflFwUploadService.EXTRA_DATA) != null)) {
                        executeReadFirmwareStatus();
                    }

                    switch (sFirmwareUpgradeTypeFlag) {
                        case 0:
                            if (mDeviceInfoCallback != null) {
                                mDeviceInfoCallback.onDeviceInfoListener(
                                        UploadCode.DEVICE_FIRMWARE_UPGRADE_TYPE_NORMAL.getCode(),
                                        "0"
                                );
                            }
                            break;

                        case 1:
                            if (mDeviceInfoCallback != null) {
                                mDeviceInfoCallback.onDeviceInfoListener(
                                        UploadCode.DEVICE_FIRMWARE_UPGRADE_TYPE_FORCED.getCode(),
                                        "1"
                                );
                            }
                            break;
                    }
                    Log.d(BLE_FOTA_TAG, "Firmware update type: " + sFirmwareUpgradeTypeFlag);

                } else if (BflFwUploadService.ACTION_RESET_WRITABLE.equals(action)) {
                    if (mDeviceInfoCallback != null) {
                        mDeviceInfoCallback.onDeviceInfoListener(
                                UploadCode.DEVICE_FIRMWARE_RESET.getCode(),
                                null
                        );
                    }
                    Log.d(BLE_FOTA_TAG, "The target device is reset");

                } else if (BflFwUploadService.ACTION_DATA_WRITABLE.equals(action)) {
                    if (mDeviceInfoCallback != null) {
                        mDeviceInfoCallback.onDeviceInfoListener(
                                UploadCode.DEVICE_EXTRA_DATA.getCode(),
                                intent.getStringExtra(BflFwUploadService.EXTRA_DATA)
                        );
                    }
                }
            } else {
                Log.e(BLE_FOTA_TAG, "Firmware uploader broadcast data is NULL.");
            }
        }
    };

    /**
     * BLE FOTA firmware upload service connection.
     * Create service connection.
     *
     * @param address is the device MAC address.
     * @param initAutoProgress is auto progress setting for initial value.
     * @see kr.co.sevencore.blefotalib.BflFwUploadService
     */
    public void connectUploadSvc(String address, boolean initAutoProgress) {
        mAddress = address;
        sInitAutoProgressFlag = initAutoProgress;

        mUploadServiceIntent = new Intent(mContext, BflFwUploadService.class);
        // BLE FOTA Library uses daemon(local) background service using startService method to run independently
        // & remote service using bindService method to communicate by AIDL.
        mContext.startService(mUploadServiceIntent);
        mContext.bindService(mUploadServiceIntent, mBflUploadSvcConnection, mContext.BIND_AUTO_CREATE);
    }

    /**
     * BLE FOTA firmware upload service disconnection.
     *
     * @see kr.co.sevencore.blefotalib.BflDeviceScanService
     * @see kr.co.sevencore.blefotalib.BflUtil
     */
    public void disconnectUploadSvc() {
        sInitAutoProgressFlag = false;

        if (BflUtil.isServiceRunning(mContext, BflFwUploadService.class)) {
            try {
                mContext.unbindService(mBflUploadSvcConnection);
                mContext.stopService(mUploadServiceIntent);
                mBflUploadBinder = null;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Create connection with the BLE device.
     *
     * @param address of the BLE device is used for creating connection.
     */
    public void connect(String address) {
        if (mBflUploadBinder != null) {
            try {
                mBflUploadBinder.connect(address);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Close connection with the BLE device.
     */
    public void disconnect() {
        if (mBflUploadBinder != null) {
            try {
                mBflUploadBinder.disconnect();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * After closing connection by disconnect method,
     * system resources have to be released properly by close method.
     */
    public void close() {
        if (mBflUploadBinder != null) {
            try {
                mBflUploadBinder.close();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Register broadcast receiver.
     *
     * @param context is gettable from the caller application.
     * @see kr.co.sevencore.blefotalib.BflFwUploadService
     */
    public void registerBflUploadReceiver(Context context) {
        context.registerReceiver(mBflUploadBroadcastReceiver, makeGattUpdateIntentFilter());
    }

    /**
     * Unregister broadcast receiver.
     *
     * @see kr.co.sevencore.blefotalib.BflFwDownloadService
     */
    public void unregisterBflUploadReceiver() {
        try {
            mContext.unregisterReceiver(mBflUploadBroadcastReceiver);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Set FOTA progress automation flag.
     *
     * @param flag is boolean value to be set.
     */
    public void setAutoProgressFlag(boolean flag) {
        sAutoProgressFlag = flag;
    }

    /**
     * Initialize FOTA progress related values.
     */
    private void initializeValue() {
        sLeftConnCnt = 0;

        sSequenceNumber = -1;
        sFirmwareUpgradeTypeFlag = 0;
    }

    /**
     * Update GATT service adapter.
     *
     * @param gattServiceData is services list.
     * @param gattCharacteristicData is characteristics list.
     * @return GATT service adapter.
     * @see kr.co.sevencore.blefotalib.BflFwUploadService
     */
    public SimpleExpandableListAdapter updateGattServicesAdapter(
            ArrayList<HashMap<String, String>> gattServiceData, ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData) {
        SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter(
                mContext,
                gattServiceData,
                android.R.layout.simple_expandable_list_item_2,
                new String[] {BflCodeList.LIST_NAME, BflCodeList.LIST_UUID},
                new int[] { android.R.id.text1, android.R.id.text2},
                gattCharacteristicData,
                android.R.layout.simple_expandable_list_item_2,
                new String[] {BflCodeList.LIST_NAME, BflCodeList.LIST_UUID},
                new int[] { android.R.id.text1, android.R.id.text2 }
        );
        return gattServiceAdapter;
    }

    /**
     * Check more data is needed to be transmitted to the target device.
     *
     * @param filePath is the location that firmware data stored.
     * @param sequenceNumber is amount of data going to be transmitted.
     * @return true: More data is needed to be transmitted.
     *         false: Firmware data transmission finished.
     */
    private boolean checkSequence(String filePath, int sequenceNumber) {
        File firmwareFile = new File(filePath);
        long length = firmwareFile.length();
        int sequence = sequenceNumber;
        int sendSize;

        if (sequence < -1) {
            sequence += 256; // Used for overflow.
        }

        if ((length % BflFwUploadService.PURE_EACH_CONN_DATA_SIZE) != 0) {
            sendSize = (sequence * BflFwUploadService.EACH_CONN_DATA_SIZE)
                    + ((int) length % BflFwUploadService.PURE_EACH_CONN_DATA_SIZE);
        } else {
            sendSize = (sequence + 1) * BflFwUploadService.EACH_CONN_DATA_SIZE;
        }

        return (sendSize >= (int) length);
    }

    /**
     * Execute the specific BLE property.
     *
     * @param serviceIdx is index of services in adapter.
     * @param characteristicIdx is index of characteristics in adapter.
     * @return false: sAutoProgressFlag is disabled or BluetoothGattCharacteristic is null.
     */
    public boolean executeProperty(int serviceIdx, int characteristicIdx) {
        if (!sAutoProgressFlag) {
            try {
                // Return true: onWriteCharacteristic | onReadCharacteristic | setCharacteristicNotification callback.
                return mBflUploadBinder.checkProperty(serviceIdx, characteristicIdx);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * 18FF FOTA service.
     * 2AF0 Firmware version characteristic.
     * READ property function of FOTA profile.
     */
    public void executeReadFirmwareCurrentVersion() {
        try {
            mBflUploadBinder.executeReadCharacteristic(
                    ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                    CharacteristicFotaIdxCode.CHARACTERISTIC_FIRMWARE_VERSION.getCode()
            );
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 18FF FOTA service.
     * 2AF1 Firmware new version characteristic
     * READ (| WRITE) property function of FOTA profile.
     */
    public void executeReadFirmwareNewVersion() {
        try {
            mBflUploadBinder.executeReadCharacteristic(
                    ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                    CharacteristicFotaIdxCode.CHARACTERISTIC_FIRMWARE_NEW_VERSION.getCode()
            );
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 18FF FOTA service.
     * 2AF3 Sequence number characteristic.
     * READ property function of FOTA profile.
     */
    public void executeReadSequenceNumber() {
        try {
            mBflUploadBinder.executeReadCharacteristic(
                    ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                    CharacteristicFotaIdxCode.CHARACTERISTIC_SEQUENCE_NUMBER.getCode()
            );
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 18FF FOTA service.
     * 2AF6 Firmware data check characteristic.
     * READ | NOTIFY property of FOTA profile.
     */
    public void executeReadFirmwareDataCheck() {
        try {
            mBflUploadBinder.executeReadCharacteristic(
                    ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                    CharacteristicFotaIdxCode.CHARACTERISTIC_FIRMWARE_DATA_CHECK.getCode()
            );
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 18FF FOTA service.
     * 2AF7 Firmware status characteristic.
     * READ | WRITE property function of FOTA profile.
     */
    public void executeReadFirmwareStatus() {
        try {
            mBflUploadBinder.executeReadCharacteristic(
                    ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                    CharacteristicFotaIdxCode.CHARACTERISTIC_FIRMWARE_STATUS.getCode()
            );
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 180A Device information which is included service.
     * 2A29 Manufacturer name characteristic.
     * READ property function of FOTA profile.
     */
    public void executeReadManufacturerName() {
        try {
            mBflUploadBinder.executeReadCharacteristic(
                    ServiceIdxCode.SERVICE_DEVICE_INFO.getCode(),
                    CharacteristicDeviceInfoIdxCode.CHARACTERISTIC_MANUFACTURER_NAME.getCode()
            );
        }catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 180A Device information which is included service.
     * 2A24 Model number characteristic.
     * READ property function of FOTA profile.
     */
    public void executeReadModelNumber() {
        try {
            mBflUploadBinder.executeReadCharacteristic(
                    ServiceIdxCode.SERVICE_DEVICE_INFO.getCode(),
                    CharacteristicDeviceInfoIdxCode.CHARACTERISTIC_MODEL_NUMBER.getCode()
            );
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 180A Device information which is included service.
     * 2A25 Serial number characteristic.
     * READ property function of FOTA profile.
     */
    public void executeReadSerialNumber() {
        try {
            mBflUploadBinder.executeReadCharacteristic(
                    ServiceIdxCode.SERVICE_DEVICE_INFO.getCode(),
                    CharacteristicDeviceInfoIdxCode.CHARACTERISTIC_SERIAL_NUMBER.getCode()
            );
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 18FF FOTA service.
     * 2AF1 Firmware new version characteristic.
     * (READ |) WRITE property function of FOTA profile.
     *
     * @param firmwareVersion is new version information of firmware data which is going to be updated.
     */
    public void executeWriteFirmwareNewVersion(String firmwareVersion) {
        try {
            mBflUploadBinder.executeWriteFirmwareNewVersion(
                    ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                    CharacteristicFotaIdxCode.CHARACTERISTIC_FIRMWARE_NEW_VERSION.getCode(),
                    firmwareVersion
            );
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 18FF FOTA service.
     * 2AF2 Firmware data characteristic.
     * WRITE property function of FOTA profile.
     *
     * @param filePath is the location of the new firmware data in the smart device.
     * @param sequenceNumber is the start location information of the firmware data which is going to be transmitted.
     */
    public void executeFirmwareUpgrade(String filePath, int sequenceNumber) {
        try {
            mBflUploadBinder.executeWriteFirmwareData(
                    ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                    CharacteristicFotaIdxCode.CHARACTERISTIC_FIRMWARE_DATA.getCode(),
                    filePath,
                    sequenceNumber
            );
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 18FF FOTA service.
     * 2AF4 Checksum data characteristic.
     * WRITE property function of FOTA profile.
     *
     * @param filePath is the location of the checksum data which is generated from firmware data.
     */
    public void executeWriteChecksumData(String filePath) {
        try {
            mBflUploadBinder.executeWriteChecksumData(
                    ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                    CharacteristicFotaIdxCode.CHARACTERISTIC_CHECKSUM_DATA.getCode(),
                    filePath
            );
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 18FF FOTA service.
     * 2AF6 Firmware upgrade type characteristic.
     * WRITE property function of FOTA profile.
     *
     * @param typeFlag is normal or forced upgrade type.
     */
    public void executeWriteFirmwareUpgradeType(byte typeFlag) {
        try {
            mBflUploadBinder.executeWriteFirmwareUpgradeType(
                    ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                    CharacteristicFotaIdxCode.CHARACTERISTIC_FIRMWARE_UPGRADE_TYPE.getCode(),
                    typeFlag
            );
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 18FF FOTA service.
     * 2AF8 Reset characteristic.
     * WRITE property function of FOTA profile.
     *
     * @param resetFlag is reset command flag.
     */
    public void executeWriteReset(byte resetFlag) {
        try {
            mBflUploadBinder.executeWriteReset(
                    ServiceIdxCode.SERVICE_FIRMWARE_UPGRADE.getCode(),
                    CharacteristicFotaIdxCode.CHARACTERISTIC_RESET.getCode(),
                    resetFlag
            );
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * Intent filter for BflFwUploadService.
     *
     * ACTION_GATT_CONNECTING: Bluetooth GATT connection creating state.
     * ACTION_GATT_CONNECTED: Bluetooth GATT connection created state.
     * ACTION_GATT_DISCONNECTING: Bluetooth GATT connection closing state.
     * ACTION_GATT_DISCONNECTED: Bluetooth GATT connection closed state.
     * ACTION_GATT_SERVICES_DISCOVERED: Bluetooth GATT service list discovered.
     * ACTION_GATT_DATA_AVAILABLE: Bluetooth GATT service list get.
     *
     * Read properties of FOTA service.
     * ACTION_FIRMWARE_CURRENT_VERSION_AVAILABLE: The target device current firmware version information.
     * ACTION_FIRMWARE_NEW_VERSION_AVAILABLE: The target device new firmware version information to be going to be updated.
     * ACTION_SEQUENCE_NUMBER_AVAILABLE: The sequence number is used to manage firmware data transmission.
     * ACTION_FIRMWARE_DATA_CHECK_AVAILABLE: Firmware data integrity information.
     * ACTION_FIRMWARE_STATUS_AVAILABLE: Firmware status information.
     *
     * Read properties of device information service.
     * ACTION_MANUFACTURER_NAME_AVAILABLE: Manufacturer information.
     * ACTION_MODEL_NUMBER_AVAILABLE: Model number information.
     * ACTION_SERIAL_NUMBER_AVAILABLE: Serial number information.
     *
     * ACTION_DATA_AVAILABLE: Read data.
     *
     * Write properties of FOTA service.
     * ACTION_FIRMWARE_NEW_VERSION_WRITABLE: Write new firmware version information.
     * ACTION_FIRMWARE_DATA_WRITABLE: Firmware data transmission.
     * ACTION_SEQUENCE_NUMBER_WRITABLE: The sequence number is used to manage firmware data transmission.
     * ACTION_CHECKSUM_DATA_WRITABLE: Checksum data checks integrity of the firmware data.
     * ACTION_FIRMWARE_UPGRADE_TYPE_WRITABLE: Apply firmware upgrade type.
     * ACTION_RESET_WRITABLE: The target device reset.
     *
     * ACTION_DATA_WRITABLE: Write data.
     *
     * @return Intent filter.
     */
    private static IntentFilter makeGattUpdateIntentFilter() {
        final IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(BflFwUploadService.ERROR_LOST_GATT);
        intentFilter.addAction(BflFwUploadService.ERROR_LOST_DEVICE_INFORMATION);

        intentFilter.addAction(BflFwUploadService.ACTION_GATT_CONNECTING);
        intentFilter.addAction(BflFwUploadService.ACTION_GATT_CONNECTED);
        intentFilter.addAction(BflFwUploadService.ACTION_GATT_DISCONNECTING);
        intentFilter.addAction(BflFwUploadService.ACTION_GATT_DISCONNECTED);
        intentFilter.addAction(BflFwUploadService.ACTION_GATT_SERVICES_DISCOVERED);
        intentFilter.addAction(BflFwUploadService.ACTION_GATT_DATA_AVAILABLE);
        // Read properties.
        intentFilter.addAction(BflFwUploadService.ACTION_FIRMWARE_CURRENT_VERSION_AVAILABLE);
        intentFilter.addAction(BflFwUploadService.ACTION_FIRMWARE_NEW_VERSION_AVAILABLE);
        intentFilter.addAction(BflFwUploadService.ACTION_SEQUENCE_NUMBER_AVAILABLE);
        intentFilter.addAction(BflFwUploadService.ACTION_FIRMWARE_DATA_CHECK_AVAILABLE);
        intentFilter.addAction(BflFwUploadService.ACTION_FIRMWARE_STATUS_AVAILABLE);
        intentFilter.addAction(BflFwUploadService.ACTION_MANUFACTURER_NAME_AVAILABLE);
        intentFilter.addAction(BflFwUploadService.ACTION_MODEL_NUMBER_AVAILABLE);
        intentFilter.addAction(BflFwUploadService.ACTION_SERIAL_NUMBER_AVAILABLE);
        intentFilter.addAction(BflFwUploadService.ACTION_DATA_AVAILABLE);
        // Write properties.
        intentFilter.addAction(BflFwUploadService.ACTION_FIRMWARE_NEW_VERSION_WRITABLE);
        intentFilter.addAction(BflFwUploadService.ACTION_FIRMWARE_DATA_WRITABLE);
        intentFilter.addAction(BflFwUploadService.ACTION_SEQUENCE_NUMBER_WRITABLE);
        intentFilter.addAction(BflFwUploadService.ACTION_CHECKSUM_DATA_WRITABLE);
        intentFilter.addAction(BflFwUploadService.ACTION_FIRMWARE_UPGRADE_TYPE_WRITABLE);
        intentFilter.addAction(BflFwUploadService.ACTION_RESET_WRITABLE);
        intentFilter.addAction(BflFwUploadService.ACTION_DATA_WRITABLE);
        return intentFilter;
    }
}