#ifndef BLUETOOTH_H
#define BLUETOOTH_H

#include <string>
#include <stdexcept>
#include <boost/uuid/uuid.hpp>
#include <boost/optional.hpp>

#include "ByteBuffer.h"

namespace trygvis {
namespace bluetooth {

using namespace std;

struct SpecUuid {
public:
    SpecUuid(uint16_t value) : value(value) {
    }

    uint16_t value;
};

namespace uuids {
const SpecUuid PRIMARY_SERVICE = SpecUuid(0x2800);
const SpecUuid SECONDARY_SERVICE = SpecUuid(0x2801);
const SpecUuid CHARACTERISTIC = SpecUuid(0x2803);
}

class BluetoothAdapter;

class BluetoothDevice;

class BluetoothGattService;

class BluetoothGattCharacteristic;

class BluetoothException : public runtime_error {
public:
    BluetoothException(const BluetoothAdapter *adapter, string const &what) :
            runtime_error(what), adapter(adapter), device(nullptr) {
    }

    BluetoothException(const BluetoothDevice *device, string const &what) :
            runtime_error(what), adapter(nullptr), device(device) {
    }

    BluetoothException(string const &what) :
            runtime_error(what), adapter(nullptr), device(nullptr) {
    }

    const BluetoothAdapter *adapter;
    const BluetoothDevice *device;
};

class Mac {
public:
    Mac(uint8_t _0, uint8_t _1, uint8_t _2, uint8_t _3, uint8_t _4, uint8_t _5) {
        bytes[0] = _0;
        bytes[1] = _1;
        bytes[2] = _2;
        bytes[3] = _3;
        bytes[4] = _4;
        bytes[5] = _5;
    };

    string str() const;

    bool operator==(Mac &other) const;

    bool operator!=(Mac &other) const;

    void copy(uint8_t &_0, uint8_t &_1, uint8_t &_2, uint8_t &_3, uint8_t &_4, uint8_t &_5) const;

    static Mac parseMac(string s);

    friend bool operator<(const Mac &a, const Mac &b);

private:
    uint8_t bytes[6];
};

class BluetoothGattCharacteristic {
public:
    virtual ~BluetoothGattCharacteristic() {
    };

    virtual BluetoothGattService &getService() const = 0;

    virtual uint16_t getHandle() const = 0;

    virtual const boost::uuids::uuid getUuid() const = 0;

    virtual uint8_t getProperties() const = 0;

    virtual uint16_t getValueHandle() const = 0;
};

class BluetoothGattService {
public:
    virtual ~BluetoothGattService() {
    };

    virtual BluetoothDevice &getDevice() const = 0;

    virtual boost::uuids::uuid getUuid() const = 0;

    virtual uint16_t getHandle() const = 0;

    virtual uint16_t getEndGroupHandle() const = 0;

    virtual const vector<BluetoothGattCharacteristic *> getCharacteristics() const = 0;

    virtual void addCharacteristic(BluetoothGattCharacteristic *characteristic) = 0;

    virtual const boost::optional<BluetoothGattCharacteristic &> findCharacteristic(boost::uuids::uuid uuid) const = 0;
};

class BluetoothGatt {
public:
    BluetoothGatt();

    virtual ~BluetoothGatt();

    virtual BluetoothDevice &getDevice() const = 0;

    virtual void connect() = 0;

    virtual void disconnect() = 0;

    virtual void writeValue(const BluetoothGattCharacteristic &c, const ByteBuffer &bytes) = 0;

    virtual ByteBuffer readValue(const BluetoothGattCharacteristic &c) = 0;

    virtual void discoverServices() = 0;

    virtual const vector<BluetoothGattService *> getServices() const = 0;

    virtual const boost::optional<BluetoothGattService &> findService(boost::uuids::uuid uuid) const = 0;
};

class BluetoothDevice {
public:
    BluetoothDevice();

    virtual ~BluetoothDevice();

    virtual Mac const &getMac() = 0;

    virtual BluetoothAdapter &getAdapter() = 0;

    virtual BluetoothGatt &connectGatt() = 0;
};

class BluetoothAdapter {
public:
    virtual void startScan() = 0;

    virtual void stopScan() = 0;

    virtual void runScan(void (callback)(BluetoothDevice &device)) = 0;

    virtual BluetoothDevice &getDevice(Mac &mac) = 0;

protected:
    BluetoothAdapter();

    virtual ~BluetoothAdapter();
};

/**
* Right this is only RAII support to properly call shutdown().
*
* TODO: move getAdapter() here. Make this control all shutdowns.
*/
class BluetoothSystem {
public:
    BluetoothSystem();

    ~BluetoothSystem();
};

enum AttPduType {
    ERROR = 0x00,
    INVALID_HANDLE = 0x01,
    READ_BY_TYPE_REQ = 0x08,
    READ_BY_TYPE_RES = 0x09,
    READ_REQ = 0x0a,
    READ_RES = 0x0b,
    READ_BY_GROUP_TYPE_REQ = 0x10,
    READ_BY_GROUP_TYPE_RES = 0x11,
    WRITE_REQ = 0x12,
    WRITE_RES = 0x13,
};

class AttributeData;

class AttPdu {
public:
    AttPdu(ByteBuffer &bytes);

    AttPdu(ByteBuffer &bytes, AttPduType type);

    AttPduType getType();

    static vector<AttributeData> parseReadByGroupType(ByteBuffer &bytes);

    static vector<AttributeData> parseReadByType(ByteBuffer &bytes);

    static void parseRead(ByteBuffer &bytes);

    static void parseWrite(ByteBuffer &bytes);

    static void makeReadByGroupType(ByteBuffer &bytes, uint16_t startHandle, uint16_t endHandle, SpecUuid uuid);

    static void makeReadByType(ByteBuffer &bytes, uint16_t startHandle, uint16_t endHandle, SpecUuid uuid);

    static void makeRead(ByteBuffer &bytes, uint16_t handle);

    static void makeWrite(ByteBuffer &req, uint16_t handle, const ByteBuffer &bytes);

private:
    static void checkType(ByteBuffer &bytes, AttPduType type);

    static vector<AttributeData> parse(ByteBuffer &bytes, AttPduType type);

    ByteBuffer &bytes;
};

class AttributeData {
public:
    ~AttributeData();

    static AttributeData fromByteBuffer(ByteBuffer &value);

    const uint16_t handle;
    ByteBuffer value;

private:
    AttributeData(uint16_t handle, ByteBuffer value);
};

BluetoothAdapter &getAdapter(int hciDevice);

void shutdown();

boost::uuids::uuid makeUuid(const boost::uuids::uuid base, uint8_t a, uint8_t b);

}
}

#endif