package com.idevicesinc.sweetblue.utils;


import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;


/**
 * Use this class to build out a GATT database for your simulated devices when unit testing. It contains builder classes to make it easier
 * to build out the database. Start by calling {@link #addService(UUID)}.
 */
public class GattDatabase
{

    private final List<BluetoothGattService> m_services;


    public GattDatabase()
    {
        m_services = new ArrayList<>();
    }

    /**
     * Add a new service to the database.
     */
    public final ServiceBuilder addService(UUID serviceUuid)
    {
        return new ServiceBuilder(this, serviceUuid);
    }

    /**
     * Return the list of {@link BluetoothGattService}s in this database.
     */
    public final List<BluetoothGattService> getServiceList()
    {
        return m_services;
    }



    private void addService(BluetoothGattService service)
    {
        m_services.add(service);
    }


    /**
     * Builder class used to help building out {@link BluetoothGattService}s.
     */
    public final static class ServiceBuilder
    {

        private final GattDatabase m_database;
        private final List<BluetoothGattCharacteristic> m_characteristics;
        private final UUID m_serviceUuid;

        private BluetoothGattService m_service;

        private boolean m_isPrimary = true;


        private ServiceBuilder(GattDatabase database, UUID serviceUuid)
        {
            m_database = database;
            m_serviceUuid = serviceUuid;
            m_characteristics = new ArrayList<>();
        }

        /**
         * Set this service to the type {@link BluetoothGattService#SERVICE_TYPE_PRIMARY}. This is the default, so it shouldn't be necessary
         * to call this, but leaving it here for explicitness.
         */
        public final ServiceBuilder primary()
        {
            m_isPrimary = true;
            return this;
        }

        /**
         * Set this service to the type {@link BluetoothGattService#SERVICE_TYPE_SECONDARY}.
         */
        public final ServiceBuilder secondary()
        {
            m_isPrimary = false;
            return this;
        }

        /**
         * Add a new {@link BluetoothGattCharacteristic} to this service.
         */
        public final CharacteristicBuilder addCharacteristic(UUID charUuid)
        {
            return new CharacteristicBuilder(this, charUuid);
        }

        /**
         * Complete this service, and create a new one to be entered into the database.
         */
        public final ServiceBuilder newService(UUID serviceUuid)
        {
            build();
            return new ServiceBuilder(m_database, serviceUuid);
        }

        /**
         * Builds the current {@link BluetoothGattService}, and returns the parent {@link GattDatabase}.
         */
        public final GattDatabase build()
        {
            m_service = new BluetoothGattService(m_serviceUuid, m_isPrimary ? BluetoothGattService.SERVICE_TYPE_PRIMARY : BluetoothGattService.SERVICE_TYPE_SECONDARY);
            for (BluetoothGattCharacteristic ch : m_characteristics)
            {
                m_service.addCharacteristic(ch);
            }
            m_database.addService(m_service);
            return m_database;
        }


        private GattDatabase getDatabase()
        {
            return m_database;
        }

        private void addCharacteristic(BluetoothGattCharacteristic characteristic)
        {
            m_characteristics.add(characteristic);
        }
    }


    /**
     * Builder class used to create and configure a {@link BluetoothGattCharacteristic} to be entered into a {@link BluetoothGattService}.
     */
    public final static class CharacteristicBuilder
    {

        private final UUID m_charUuid;
        private final ServiceBuilder m_serviceBuilder;
        private final List<BluetoothGattDescriptor> m_descriptors;

        private BluetoothGattCharacteristic m_characteristic;

        private byte[] m_value;
        private int m_properties;
        private int m_permissions;


        private CharacteristicBuilder(ServiceBuilder serviceBuilder, UUID charUuid)
        {
            m_serviceBuilder = serviceBuilder;
            m_charUuid = charUuid;
            m_descriptors = new ArrayList<>();
        }

        /**
         * Set this {@link BluetoothGattCharacteristic}'s properties. You can also use {@link #setProperties(int...)}, but it's recommended
         * you use {@link #setProperties()} instead.
         */
        public final CharacteristicBuilder setProperties(int properties)
        {
            m_properties = properties;
            return this;
        }

        /**
         * Set this {@link BluetoothGattCharacteristic}'s properties. You can also use {@link #setProperties(int)}, but it's recommended
         * you use {@link #setProperties()} instead.
         */
        public final CharacteristicBuilder setProperties(int... properties)
        {
            m_properties = 0;
            if (properties != null && properties.length > 0)
            {
                for (int prop : properties)
                {
                    m_properties |= prop;
                }
            }
            return this;
        }

        /**
         * Set the properties for this {@link BluetoothGattCharacteristic}.
         */
        public final Properties setProperties()
        {
            return new Properties(this);
        }

        /**
         * Set this {@link BluetoothGattCharacteristic}'s permissions. You can also use {@link #setPermissions(int...)}, but it's recommended
         * you use {@link #setPermissions()} instead.
         */
        public final CharacteristicBuilder setPermissions(int permissions)
        {
            m_permissions = permissions;
            return this;
        }

        /**
         * Set this {@link BluetoothGattCharacteristic}'s permissions. You can also use {@link #setPermissions(int)}, but it's recommended
         * you use {@link #setPermissions()} instead.
         */
        public final CharacteristicBuilder setPermissions(int... permissions)
        {
            m_permissions = 0;
            if (permissions != null && permissions.length > 0)
            {
                for (int perm : permissions)
                {
                    m_permissions |= perm;
                }
            }
            return this;
        }

        /**
         * Set the permissions for this {@link BluetoothGattCharacteristic}.
         */
        public final CharacteristicPermissions setPermissions()
        {
            return new CharacteristicPermissions(this);
        }

        /**
         * Set the default value for this {@link BluetoothGattCharacteristic}.
         */
        public final CharacteristicBuilder setValue(byte[] value)
        {
            m_value = value;
            return this;
        }

        /**
         * Add a new {@link BluetoothGattDescriptor} to be added to this {@link BluetoothGattCharacteristic}.
         */
        public final DescriptorBuilder addDescriptor(UUID descriptorUuid)
        {
            return new DescriptorBuilder(this, descriptorUuid);
        }

        /**
         * Build this {@link BluetoothGattCharacteristic}, and add it to it's parent {@link BluetoothGattService}.
         */
        public final ServiceBuilder build()
        {
            m_characteristic = new BluetoothGattCharacteristic(m_charUuid, m_properties, m_permissions);
            m_characteristic.setValue(m_value);
            for (BluetoothGattDescriptor desc : m_descriptors)
            {
                m_characteristic.addDescriptor(desc);
            }
            m_serviceBuilder.addCharacteristic(m_characteristic);
            return m_serviceBuilder;
        }

        /**
         * Calls {@link #build()}, then creates a new {@link BluetoothGattCharacteristic}.
         */
        public final CharacteristicBuilder newCharacteristic(UUID charUuid)
        {
            build();
            return new CharacteristicBuilder(m_serviceBuilder, charUuid);
        }

        /**
         * Calls {@link #build()}, then builds the parent {@link BluetoothGattService}, and add it to the database.
         */
        public final GattDatabase completeService()
        {
            return build().build();
        }



        private void addDescriptor(BluetoothGattDescriptor descriptor)
        {
            m_descriptors.add(descriptor);
        }
    }

    /**
     * Builder class used to create and configure a {@link BluetoothGattDescriptor} to be added to a {@link BluetoothGattCharacteristic}.
     */
    public final static class DescriptorBuilder
    {

        private final UUID m_descUuid;
        private final CharacteristicBuilder m_charBuilder;

        private BluetoothGattDescriptor m_descriptor;

        private int m_permissions;
        private byte[] m_value;


        private DescriptorBuilder(CharacteristicBuilder charBuilder, UUID descUuid)
        {
            m_descUuid = descUuid;
            m_charBuilder = charBuilder;
        }

        /**
         * Set this {@link BluetoothGattDescriptor}'s permissions. You can also use {@link #setPermissions(int...)}, but it's recommended
         * you use {@link #setPermissions()} instead.
         */
        public final DescriptorBuilder setPermissions(int permissions)
        {
            m_permissions = permissions;
            return this;
        }

        /**
         * Set this {@link BluetoothGattDescriptor}'s permissions. You can also use {@link #setPermissions(int)}, but it's recommended
         * you use {@link #setPermissions()} instead.
         */
        public final DescriptorBuilder setPermissions(int... permissions)
        {
            m_permissions = 0;
            if (permissions != null && permissions.length > 0)
            {
                for (int perm : permissions)
                {
                    m_permissions |= perm;
                }
            }
            return this;
        }

        public final DescriptorBuilder setValue(byte[] value)
        {
            m_value = value;
            return this;
        }

        /**
         * Set the permissions for this {@link BluetoothGattDescriptor}.
         */
        public final DescriptorPermissions setPermissions()
        {
            return new DescriptorPermissions(this);
        }

        /**
         * Build this {@link BluetoothGattDescriptor}, and add it to its parent {@link BluetoothGattCharacteristic}.
         */
        public final CharacteristicBuilder build()
        {
            m_descriptor = new BluetoothGattDescriptor(m_descUuid, m_permissions);
            m_descriptor.setValue(m_value);
            m_charBuilder.addDescriptor(m_descriptor);
            return m_charBuilder;
        }

        /**
         * Calls {@link #build()}, then creates a new {@link BluetoothGattDescriptor} to add to the same {@link BluetoothGattCharacteristic}.
         */
        public final DescriptorBuilder newDescriptor(UUID descriptorUuid)
        {
            build();
            return new DescriptorBuilder(m_charBuilder, descriptorUuid);
        }

        /**
         * Calls {@link #build()}, then builds the parent {@link BluetoothGattCharacteristic}.
         */
        public final ServiceBuilder completeChar()
        {
            return build().build();
        }

        /**
         * Calls {@link #build()}, then builds the parent {@link BluetoothGattCharacteristic}, then builds the parent {@link BluetoothGattService}.
         */
        public final GattDatabase completeService()
        {
            return build().completeService();
        }

    }


    public static class Properties
    {

        private final CharacteristicBuilder m_charBuilder;
        private int m_properties;


        private Properties(CharacteristicBuilder builder)
        {
            m_charBuilder = builder;
        }


        public final Properties read()
        {
            m_properties |= BluetoothGattCharacteristic.PROPERTY_READ;
            return this;
        }

        public final Properties write()
        {
            m_properties |= BluetoothGattCharacteristic.PROPERTY_WRITE;
            return this;
        }

        public final Properties readWrite()
        {
            return read().write();
        }

        public final Properties readWriteNotify()
        {
            return readWrite().notify_prop();
        }

        public final Properties readWriteIndicate()
        {
            return readWrite().indicate();
        }

        public final Properties signed_write()
        {
            m_properties |= BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE;
            return this;
        }

        public final Properties write_no_response()
        {
            m_properties |= BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE;
            return this;
        }

        public final Properties notify_prop()
        {
            m_properties |= BluetoothGattCharacteristic.PROPERTY_NOTIFY;
            return this;
        }

        public final Properties indicate()
        {
            m_properties |= BluetoothGattCharacteristic.PROPERTY_INDICATE;
            return this;
        }

        public final Properties broadcast()
        {
            m_properties |= BluetoothGattCharacteristic.PROPERTY_BROADCAST;
            return this;
        }

        public final Properties extended_props()
        {
            m_properties |= BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS;
            return this;
        }

        public final CharacteristicBuilder build()
        {
            m_charBuilder.setProperties(m_properties);
            return m_charBuilder;
        }

        public final CharacteristicPermissions setPermissions()
        {
            return new CharacteristicPermissions(build());
        }

        public final GattDatabase completeService()
        {
            return build().completeService();
        }
    }


    public static class Permissions<T extends Permissions>
    {

        private int m_permissions;


        public final T read()
        {
            m_permissions |= BluetoothGattCharacteristic.PERMISSION_READ;
            return (T) this;
        }

        public final T read_encrypted()
        {
            m_permissions |= BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED;
            return (T) this;
        }

        public final T read_encrypted_mitm()
        {
            m_permissions |= BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED_MITM;
            return (T) this;
        }

        public final T write()
        {
            m_permissions |= BluetoothGattCharacteristic.PERMISSION_WRITE;
            return (T) this;
        }

        public final T readWrite()
        {
            return (T) read().write();
        }

        public final T signed_write()
        {
            m_permissions |= BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED;
            return (T) this;
        }

        public final T signed_write_mitm()
        {
            m_permissions |= BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED_MITM;
            return (T) this;
        }

        public final T write_encrypted()
        {
            m_permissions |= BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED;
            return (T) this;
        }

        public final T write_encrypted_mitm()
        {
            m_permissions |= BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED_MITM;
            return (T) this;
        }


        final int getPermissions()
        {
            return m_permissions;
        }

    }

    public final static class CharacteristicPermissions extends Permissions<CharacteristicPermissions>
    {

        private final CharacteristicBuilder m_charBuilder;


        private CharacteristicPermissions(CharacteristicBuilder builder)
        {
            m_charBuilder = builder;
        }

        public final Properties setProperties()
        {
            return new Properties(build());
        }

        public final CharacteristicBuilder build()
        {
            m_charBuilder.setPermissions(getPermissions());
            return m_charBuilder;
        }

        public final ServiceBuilder completeChar()
        {
            return build().build();
        }

        public final GattDatabase completeService()
        {
            return build().completeService();
        }
    }

    public final static class DescriptorPermissions extends Permissions<DescriptorPermissions>
    {

        private final DescriptorBuilder m_descBuilder;


        private DescriptorPermissions(DescriptorBuilder builder)
        {
            m_descBuilder = builder;
        }


        public final DescriptorBuilder build()
        {
            m_descBuilder.setPermissions(getPermissions());
            return m_descBuilder;
        }

        public final CharacteristicBuilder completeDesc()
        {
            return build().build();
        }

        public final ServiceBuilder completeChar()
        {
            return build().completeChar();
        }

        public final GattDatabase completeService()
        {
            return build().completeService();
        }

    }

}