package com.idevicesinc.sweetblue;


import android.annotation.TargetApi;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import android.os.Build;
import android.os.DeadObjectException;
import com.idevicesinc.sweetblue.compat.K_Util;
import com.idevicesinc.sweetblue.compat.L_Util;
import com.idevicesinc.sweetblue.utils.Utils;
import java.lang.reflect.Field;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.UUID;


@TargetApi(Build.VERSION_CODES.KITKAT)
final class P_AndroidGatt implements P_GattLayer
{

    private static final String FIELD_NAME_AUTH_RETRY = "mAuthRetry";
    private static final String FIELD_NAME_NOUGAT_AUTH_RETRY_STATE = "mAuthRetryState";

    private static String s_authRetryFieldName;
    private static boolean s_nougatMr2 = false;

    private Field m_authRetryField = null;

    private BluetoothGatt m_gatt;
    private final BleDevice m_device;


    P_AndroidGatt(BleDevice device)
    {
        m_device = device;
    }


    @Override
    public final BleDevice getBleDevice()
    {
        return m_device;
    }

    @Override
    public final void setGatt(BluetoothGatt gatt)
    {
        m_gatt = gatt;
    }

    @Override
    public final BluetoothGatt getGatt()
    {
        return m_gatt;
    }

    @Override
    public final Boolean getAuthRetryValue()
    {
        if (m_gatt != null)
        {
            final Boolean result;
            if (m_authRetryField == null)
            {
                if (s_authRetryFieldName == null)
                {
                    s_authRetryFieldName = getAuthRetryName();
                    if (s_authRetryFieldName == null)
                    {
                        getManager().ASSERT(false, "Unable to get auth retry field! Neither mAuthRetry, or mAuthRetryState exist! This shouldn't happen!");
                        return null;
                    }
                }
                try
                {
                    m_authRetryField = m_gatt.getClass().getDeclaredField(s_authRetryFieldName);
                } catch (NoSuchFieldException e1)
                {
                    getManager().ASSERT(false, "Problem getting field " + m_gatt.getClass().getSimpleName() + "." + s_authRetryFieldName);
                }
            }
            try
            {
                final boolean isAccessible_saved = m_authRetryField.isAccessible();
                m_authRetryField.setAccessible(true);
                if (s_nougatMr2)
                {
                    int state = m_authRetryField.getInt(m_gatt);

                    // TODO - Refactor this to pass along with auth retry state it is, so we know how better to deal with it. (Is simply bonding enough?).
                    result = state != BleStatuses.AUTH_RETRY_STATE_IDLE;
                }
                else
                {
                    result = m_authRetryField.getBoolean(m_gatt);
                }
                m_authRetryField.setAccessible(isAccessible_saved);

                return result;
            } catch (Exception e)
            {
                getManager().ASSERT(false, "Unable to get value of " + m_gatt.getClass().getSimpleName() + "." + m_authRetryField.getName());
            }
        }
        else
        {
            getManager().ASSERT(false, "Expected gatt object to be not null");
        }
        return null;
    }

    private String getAuthRetryName()
    {
        Field[] fields = m_gatt.getClass().getDeclaredFields();
        for (Field f : fields)
        {
            if (f.getName().equals(FIELD_NAME_AUTH_RETRY))
            {
                return FIELD_NAME_AUTH_RETRY;
            }
            else if (f.getName().equals(FIELD_NAME_NOUGAT_AUTH_RETRY_STATE))
            {
                s_nougatMr2 = true;
                return FIELD_NAME_NOUGAT_AUTH_RETRY_STATE;
            }
        }
        return null;
    }

    @Override
    public final boolean equals(BluetoothGatt gatt)
    {
        return gatt == m_gatt;
    }

    private BleManager getManager()
    {
        return BleManager.s_instance;
    }

    @Override
    public final BleManager.UhOhListener.UhOh closeGatt()
    {
        BleManager.UhOhListener.UhOh uhoh = null;
        if (m_gatt == null) return uhoh;

        //--- DRK > Tried this to see if it would kill autoConnect, but alas it does not, at least on S5.
        //---		Don't want to keep it here because I'm afraid it has a better chance to do bad than good.
//			if( disconnectAlso )
//			{
//				m_gatt.disconnect();
//			}

        //--- DRK > This can randomly throw an NPE down stream...NOT from m_gatt being null, but a few methods downstream.
        //---		See below for more info.
        try
        {
            m_gatt.close();
        } catch (Exception e)
        {
            if (e instanceof DeadObjectException)
            {
                //--- RB > It has been observed by some customers that a DeadObjectException can happen here. Nothing we can do about it, just
                // checking for it, and throwing to the UhOh Listener as a DeadObjectException

//				android.os.DeadObjectException
//				at android.os.BinderProxy.transactNative(Native Method)
//				at android.os.BinderProxy.transact(Binder.java:503)
//				at android.bluetooth.IBluetoothGatt$Stub$Proxy.unregisterClient(IBluetoothGatt.java:1009)
//				at android.bluetooth.BluetoothGatt.unregisterApp(BluetoothGatt.java:820)
//				at android.bluetooth.BluetoothGatt.close(BluetoothGatt.java:759)
//				at com.idevicesinc.sweetblue.P_NativeDeviceWrapper.closeGatt(P_NativeDeviceWrapper.java:319)
//				at com.idevicesinc.sweetblue.P_NativeDeviceWrapper.closeGattIfNeeded(P_NativeDeviceWrapper.java:301)
//				at com.idevicesinc.sweetblue.BleDevice.onNativeConnectFail(BleDevice.java:5782)
//				at com.idevicesinc.sweetblue.P_BleDevice_Listeners$1.onStateChange(P_BleDevice_Listeners.java:51)
//				at com.idevicesinc.sweetblue.PA_Task.setState(PA_Task.java:148)
//				at com.idevicesinc.sweetblue.PA_Task.setEndingState(PA_Task.java:288)
//				at com.idevicesinc.sweetblue.P_TaskQueue.endCurrentTask(P_TaskQueue.java:288)
//				at com.idevicesinc.sweetblue.P_TaskQueue.tryEndingTask_mainThread(P_TaskQueue.java:395)
//				at com.idevicesinc.sweetblue.P_TaskQueue.tryEndingTask(P_TaskQueue.java:387)
//				at com.idevicesinc.sweetblue.PA_Task.timeout(PA_Task.java:183)
//				at com.idevicesinc.sweetblue.PA_Task.update_internal(PA_Task.java:354)
//				at com.idevicesinc.sweetblue.P_TaskQueue.update(P_TaskQueue.java:236)
//				at com.idevicesinc.sweetblue.BleManager.update(BleManager.java:3245)
//				at com.idevicesinc.sweetblue.BleManager$1.onUpdate(BleManager.java:746)
//				at com.idevicesinc.sweetblue.utils.UpdateLoop$1.run(UpdateLoop.java:24)
//				at android.os.Handler.handleCallback(Handler.java:739)
//				at android.os.Handler.dispatchMessage(Handler.java:95)
//				at android.os.Looper.loop(Looper.java:148)
//				at android.app.ActivityThread.main(ActivityThread.java:7303)
//				at java.lang.reflect.Method.invoke(Native Method)
//				at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
//				at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
                uhoh = BleManager.UhOhListener.UhOh.DEAD_OBJECT_EXCEPTION;
            }
            else
            {
                //--- DRK > From Flurry crash reports...happened several times on S4 running 4.4.4 but was not able to reproduce.
//				This error occurred: java.lang.NullPointerException
//				android.os.Parcel.readException(Parcel.java:1546)
//				android.os.Parcel.readException(Parcel.java:1493)
//				android.bluetooth.IBluetoothGatt$Stub$Proxy.unregisterClient(IBluetoothGatt.java:905)
//				android.bluetooth.BluetoothGatt.unregisterApp(BluetoothGatt.java:710)
//				android.bluetooth.BluetoothGatt.close(BluetoothGatt.java:649)
//				com.idevicesinc.sweetblue.P_NativeDeviceWrapper.closeGatt(P_NativeDeviceWrapper.java:238)
//				com.idevicesinc.sweetblue.P_NativeDeviceWrapper.closeGattIfNeeded(P_NativeDeviceWrapper.java:221)
//				com.idevicesinc.sweetblue.BleDevice.onNativeConnectFail(BleDevice.java:2193)
//				com.idevicesinc.sweetblue.P_BleDevice_Listeners$1.onStateChange_synchronized(P_BleDevice_Listeners.java:78)
//				com.idevicesinc.sweetblue.P_BleDevice_Listeners$1.onStateChange(P_BleDevice_Listeners.java:49)
//				com.idevicesinc.sweetblue.PA_Task.setState(PA_Task.java:118)
//				com.idevicesinc.sweetblue.PA_Task.setEndingState(PA_Task.java:242)
//				com.idevicesinc.sweetblue.P_TaskQueue.endCurrentTask(P_TaskQueue.java:220)
//				com.idevicesinc.sweetblue.P_TaskQueue.tryEndingTask(P_TaskQueue.java:267)
//				com.idevicesinc.sweetblue.P_TaskQueue.fail(P_TaskQueue.java:260)
//				com.idevicesinc.sweetblue.P_BleDevice_Listeners.onConnectionStateChange_synchronized(P_BleDevice_Listeners.java:168)
                uhoh = BleManager.UhOhListener.UhOh.RANDOM_EXCEPTION;
            }
        }
        m_gatt = null;
        return uhoh;
    }

    @Override
    public final List<BluetoothGattService> getNativeServiceList(P_Logger logger)
    {
        if (m_gatt == null)
        {
            return null;
        }
        List<BluetoothGattService> list_native = null;

        try
        {
            list_native = m_gatt.getServices();
        } catch (Exception e)
        {
            BleManager.UhOhListener.UhOh uhoh;
            if (e instanceof ConcurrentModificationException)
            {
                uhoh = BleManager.UhOhListener.UhOh.CONCURRENT_EXCEPTION;
            }
            else
            {
                uhoh = BleManager.UhOhListener.UhOh.RANDOM_EXCEPTION;
            }
            m_device.getManager().uhOh(uhoh);
            logger.e("Got a " + e.getClass().getSimpleName() + " with a message of " + e.getMessage() + " when trying to get the list of native services!");
        }
        return list_native;
    }

    @Override
    public BleServiceWrapper getBleService(UUID serviceUuid, P_Logger logger)
    {
        BleServiceWrapper wService;
        final BluetoothGattService service;
        try
        {
            service = m_gatt.getService(serviceUuid);
            wService = new BleServiceWrapper(service);
        }
        catch (Exception e)
        {
            BleManager.UhOhListener.UhOh uhoh;
            if (e instanceof ConcurrentModificationException)
            {
                uhoh = BleManager.UhOhListener.UhOh.CONCURRENT_EXCEPTION;
            }
            else
            {
                uhoh = BleManager.UhOhListener.UhOh.RANDOM_EXCEPTION;
            }
            m_device.getManager().uhOh(uhoh);
            wService = new BleServiceWrapper(uhoh);
            logger.e("Got a " + e.getClass().getSimpleName() + " with a message of " + e.getMessage() + " when trying to get the native service!");
        }
        return wService;
    }

    @Override public BluetoothGattService getService(UUID serviceUuid, P_Logger logger)
    {
        return getBleService(serviceUuid, logger).getService();
    }

    @Override
    public final boolean isGattNull()
    {
        return m_gatt == null;
    }

    @Override
    public final BluetoothGatt connect(P_NativeDeviceLayer device, Context context, boolean useAutoConnect, BluetoothGattCallback callback)
    {
        m_gatt = device.connect(context, useAutoConnect, callback);
        return m_gatt;
    }

    @Override
    public final boolean requestMtu(int mtu)
    {
        if (m_gatt != null)
        {
            return L_Util.requestMtu(m_gatt, mtu);
        }
        return false;
    }

    @Override
    public final boolean refreshGatt()
    {
        if (m_gatt != null)
        {
            Utils.refreshGatt(m_gatt);
        }
        return false;
    }

    @Override
    public final boolean requestConnectionPriority(BleConnectionPriority priority)
    {
        if (m_gatt != null)
        {
            return L_Util.requestConnectionPriority(m_gatt, priority.getNativeMode());
        }
        return false;
    }

    @Override
    public final void disconnect()
    {
        if (m_gatt != null)
        {
            m_gatt.disconnect();
        }
    }

    @Override
    public final boolean readCharacteristic(BluetoothGattCharacteristic characteristic)
    {
        if (m_gatt != null && characteristic != null)
        {
            return m_gatt.readCharacteristic(characteristic);
        }
        return false;
    }

    @Override
    public final boolean setCharValue(BluetoothGattCharacteristic characteristic, byte[] data)
    {
        if (characteristic != null)
        {
            return characteristic.setValue(data);
        }
        return false;
    }

    @Override
    public final boolean writeCharacteristic(BluetoothGattCharacteristic characteristic)
    {
        if (m_gatt != null && characteristic != null)
        {
            return m_gatt.writeCharacteristic(characteristic);
        }
        return false;
    }

    @Override
    public final boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enable)
    {
        if (m_gatt != null && characteristic != null)
        {
            return m_gatt.setCharacteristicNotification(characteristic, enable);
        }
        return false;
    }

    @Override
    public final boolean readDescriptor(BluetoothGattDescriptor descriptor)
    {
        if (m_gatt != null && descriptor != null)
        {
            return m_gatt.readDescriptor(descriptor);
        }
        return false;
    }

    @Override
    public final boolean setDescValue(BluetoothGattDescriptor descriptor, byte[] data)
    {
        if (descriptor != null)
        {
            return descriptor.setValue(data);
        }
        return false;
    }

    @Override
    public final boolean writeDescriptor(BluetoothGattDescriptor descriptor)
    {
        if (m_gatt != null && descriptor != null)
        {
            return m_gatt.writeDescriptor(descriptor);
        }
        return false;
    }

    @Override
    public final boolean discoverServices()
    {
        if (m_gatt != null)
            return m_gatt.discoverServices();
        return false;
    }

    @Override
    public final boolean executeReliableWrite()
    {
        if (m_gatt != null)
            return m_gatt.executeReliableWrite();
        return false;
    }

    @Override
    public final boolean beginReliableWrite()
    {
        if (m_gatt != null)
            return m_gatt.beginReliableWrite();
        return false;
    }

    @Override
    public final void abortReliableWrite(BluetoothDevice device)
    {
        if (m_gatt != null)
        {
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2)
            {
                m_gatt.abortReliableWrite(device);
            }
            else
            {
                K_Util.abortReliableWrite(m_gatt);
            }
        }
    }

    @Override
    public final boolean readRemoteRssi()
    {
        if (m_gatt != null)
            return m_gatt.readRemoteRssi();
        return false;
    }

}