package club.clubk.ktag.apps.core.ble;

import android.bluetooth.le.ScanResult;

import static club.clubk.ktag.apps.core.HexUtils.hexStringToByteArray;

public class Packet {

    // Packet Types
    public static final byte PACKET_TYPE_UNKNOWN = 0;
    public static final byte PACKET_TYPE_INSTIGATING_GAME = 1;
    public static final byte PACKET_TYPE_EVENT = 2;
    public static final byte PACKET_TYPE_TAG = 3;
    public static final byte PACKET_TYPE_CONSOLE = 4;
    public static final byte PACKET_TYPE_STATUS = 5;
    public static final byte PACKET_TYPE_PARAMETERS = 6;
    public static final byte PACKET_TYPE_HELLO = 7;

    // Event IDs
    public static final int EVENT_NO_EVENT = 0;
    public static final int EVENT_CONFIGURE = 1;
    public static final int EVENT_CONFIGURED = 2;
    public static final int EVENT_WRAPUP_COMPLETE = 3;
    public static final int EVENT_GAME_OVER = 4;
    public static final int EVENT_QUIET = 5;
    public static final int EVENT_UNQUIET = 6;
    public static final int EVENT_FORCE_STATE = 7;

    // Parameter subtypes
    public static final int PARAMETER_SUBTYPE_REQUEST_CURRENT = 0x00;
    public static final int PARAMETER_SUBTYPE_CURRENT_INFO = 0x01;
    public static final int PARAMETER_SUBTYPE_REQUEST_CHANGE = 0x02;
    public static final int PARAMETER_SUBTYPE_ACKNOWLEDGE_CHANGE = 0x03;
    public static final int PARAMETER_SUBTYPE_ERROR_CHANGING = 0x04;
    public static final int PARAMETER_SUBTYPE_ERROR_RESPONDING = 0xFF;

    // Parameter keys
    public static final int PARAMETER_KEY_NONE = 0;
    public static final int PARAMETER_KEY_TEAM_ID = 1;
    public static final int PARAMETER_KEY_PLAYER_ID = 2;
    public static final int PARAMETER_KEY_GAME_LENGTH = 3;
    public static final int PARAMETER_KEY_MAX_HEALTH = 4;
    public static final int PARAMETER_KEY_SECONDARY_COLOR = 5;
    public static final int PARAMETER_KEY_SPECIAL_WEAPONS_ON_REENTRY = 6;
    public static final int PARAMETER_KEY_SHOTS_FIRED_THIS_GAME = 10001;
    public static final int PARAMETER_KEY_TAGS_RECEIVED_THIS_GAME = 10002;
    public static final int PARAMETER_KEY_TIMES_TAGGED_OUT_THIS_GAME = 10003;

    // Device types
    public static final int DEVICE_TYPE_LITTLE_BOY_BLUE = 0x0000;
    public static final int DEVICE_TYPE_2020TPC = 0x0001;
    public static final int DEVICE_TYPE_MOBILE_APP = 0x0002;
    public static final int DEVICE_TYPE_32ESPECIAL = 0x0003;
    public static final int DEVICE_TYPE_UNKNOWN = 0xFFFF;

    public static String getDeviceTypeName(int deviceType) {
        switch (deviceType) {
            case DEVICE_TYPE_LITTLE_BOY_BLUE: return "Little Boy BLuE";
            case DEVICE_TYPE_2020TPC: return "2020TPC";
            case DEVICE_TYPE_MOBILE_APP: return "Mobile App";
            case DEVICE_TYPE_32ESPECIAL: return "32ESPecial";
            default: return "Unknown";
        }
    }

    public static boolean IsKTagPacket(ScanResult packet) {
        boolean result = false;

        byte[] bytes = packet.getScanRecord().getManufacturerSpecificData(0xFFFF);

        if (bytes != null) {
            if (bytes.length > 4) {
                if ((bytes[0] == 'K') && (bytes[1] == 'T') && (bytes[2] == 'a') && (bytes[3] == 'g')) {
                    result = true;
                }
            }
        }

        return result;
    }

    public static boolean IsKTagInstigatingGamePacket(ScanResult packet) {
        return IsKTagPacketOfType(packet, PACKET_TYPE_INSTIGATING_GAME);
    }

    public static boolean IsKTagEventPacket(ScanResult packet) {
        return IsKTagPacketOfType(packet, PACKET_TYPE_EVENT);
    }

    public static boolean IsKTagTagPacket(ScanResult packet) {
        return IsKTagPacketOfType(packet, PACKET_TYPE_TAG);
    }

    public static boolean IsKTagConsolePacket(ScanResult packet) {
        return IsKTagPacketOfType(packet, PACKET_TYPE_CONSOLE);
    }

    public static boolean IsKTagStatusPacket(ScanResult packet) {
        return IsKTagPacketOfType(packet, PACKET_TYPE_STATUS);
    }

    public static boolean IsKTagParametersPacket(ScanResult packet) {
        return IsKTagPacketOfType(packet, PACKET_TYPE_PARAMETERS);
    }

    public static boolean IsKTagHelloPacket(ScanResult packet) {
        return IsKTagPacketOfType(packet, PACKET_TYPE_HELLO);
    }

    private static boolean IsKTagPacketOfType(ScanResult packet, byte packetType) {
        boolean result = false;

        byte[] bytes = packet.getScanRecord().getManufacturerSpecificData(0xFFFF);

        if (bytes != null) {
            if (bytes.length > 4) {
                if ((bytes[0] == 'K') && (bytes[1] == 'T') && (bytes[2] == 'a') && (bytes[3] == 'g') && (bytes[4] == packetType)) {
                    result = true;
                }
            }
        }

        return result;
    }

    public static class InstigatingGame {
        byte[] BD_ADDR;
        int RSSI;
        int event_number;
        int game_length_ms;
        int time_until_countdown_ms;
        int random_time_after_countdown_ds;
        byte[] unused;

        static byte InstigatingGameEventNumber = 0;

        public InstigatingGame(int game_length_ms, int time_until_countdown_ms)
        {
            this.game_length_ms = game_length_ms;
            this.time_until_countdown_ms = time_until_countdown_ms;
            this.random_time_after_countdown_ds = 0;
        }

        public InstigatingGame(ScanResult packet) {
            BD_ADDR = hexStringToByteArray(packet.getDevice().getAddress(), ':');
            RSSI = packet.getRssi();

            byte[] bytes = packet.getScanRecord().getManufacturerSpecificData(0xFFFF);
            event_number = bytes[5];
            game_length_ms = ((bytes[9] & 0xFF) << 24) | ((bytes[8] & 0xFF) << 16) | ((bytes[7] & 0xFF) << 8) | (bytes[6] & 0xFF);
            time_until_countdown_ms = ((bytes[13] & 0xFF) << 24) | ((bytes[12] & 0xFF) << 16) | ((bytes[11] & 0xFF) << 8) | (bytes[10] & 0xFF);
            random_time_after_countdown_ds = bytes[14] & 0xFF;
        }

        public byte[] getBD_ADDR() { return BD_ADDR; }
        public int getGame_length_ms() { return game_length_ms; }
        public int getTime_until_countdown_ms() { return time_until_countdown_ms; }

        public byte[] GetBytes()
        {
            byte[] DataBytes = {(byte) 'K',
                    (byte) 'T',
                    (byte) 'a',
                    (byte) 'g',
                    (byte) PACKET_TYPE_INSTIGATING_GAME,
                    (byte) InstigatingGameEventNumber++,
                    (byte) ((game_length_ms >> 0) & 0xFF),
                    (byte) ((game_length_ms >> 8) & 0xFF),
                    (byte) ((game_length_ms >> 16) & 0xFF),
                    (byte) ((game_length_ms >> 24) & 0xFF),
                    (byte) ((time_until_countdown_ms >> 0) & 0xFF),
                    (byte) ((time_until_countdown_ms >> 8) & 0xFF),
                    (byte) ((time_until_countdown_ms >> 16) & 0xFF),
                    (byte) ((time_until_countdown_ms >> 24) & 0xFF),
                    (byte) random_time_after_countdown_ds,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF};

            return DataBytes;
        }
    }

    public static class Event {
        byte[] BD_ADDR;
        int RSSI;
        int event_number;
        byte[] target_BD_ADDR;
        int event_ID;
        int event_data;

        static byte EventEventNumber = 0;

        public Event(byte[] target_BD_ADDR, int event_ID, int event_data) {
            this.target_BD_ADDR = new byte[6];
            for (int i = 0; i < 6; i++) {
                this.target_BD_ADDR[i] = target_BD_ADDR[5 - i];
            }
            this.event_ID = event_ID;
            this.event_data = event_data;
        }

        public Event(ScanResult packet) {
            BD_ADDR = hexStringToByteArray(packet.getDevice().getAddress(), ':');
            RSSI = packet.getRssi();

            byte[] bytes = packet.getScanRecord().getManufacturerSpecificData(0xFFFF);
            event_number = bytes[5];
            target_BD_ADDR = new byte[6];
            for (int i = 0; i < 6; i++) {
                target_BD_ADDR[i] = bytes[6 + i];
            }
            event_ID = ((bytes[15] & 0xFF) << 24) | ((bytes[14] & 0xFF) << 16) | ((bytes[13] & 0xFF) << 8) | (bytes[12] & 0xFF);
            event_data = ((bytes[19] & 0xFF) << 24) | ((bytes[18] & 0xFF) << 16) | ((bytes[17] & 0xFF) << 8) | (bytes[16] & 0xFF);
        }

        public byte[] GetBytes() {
            byte[] DataBytes = {(byte) 'K',
                    (byte) 'T',
                    (byte) 'a',
                    (byte) 'g',
                    (byte) PACKET_TYPE_EVENT,
                    (byte) EventEventNumber++,
                    (byte) target_BD_ADDR[0],
                    (byte) target_BD_ADDR[1],
                    (byte) target_BD_ADDR[2],
                    (byte) target_BD_ADDR[3],
                    (byte) target_BD_ADDR[4],
                    (byte) target_BD_ADDR[5],
                    (byte) ((event_ID >> 0) & 0xFF),
                    (byte) ((event_ID >> 8) & 0xFF),
                    (byte) ((event_ID >> 16) & 0xFF),
                    (byte) ((event_ID >> 24) & 0xFF),
                    (byte) ((event_data >> 0) & 0xFF),
                    (byte) ((event_data >> 8) & 0xFF),
                    (byte) ((event_data >> 16) & 0xFF),
                    (byte) ((event_data >> 24) & 0xFF),
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF};

            return DataBytes;
        }

        public byte[] getBD_ADDR() { return BD_ADDR; }
        public byte[] getTarget_BD_ADDR() { return target_BD_ADDR; }
        public int getEvent_ID() { return event_ID; }
        public int getEvent_data() { return event_data; }
    }

    public static class Status {
        byte[] BD_ADDR;
        int RSSI;
        int event_number;
        int tx_power_level;
        int protocol;
        byte team_ID;
        int player_ID;
        int health;
        int maximum_health;
        int primary_color;
        int secondary_color;
        int SystemK_top_level_state;
        byte[] unused;
        public Status(ScanResult packet) {
            BD_ADDR = hexStringToByteArray(packet.getDevice().getAddress(), ':');
            RSSI = packet.getRssi();

            byte[] bytes = packet.getScanRecord().getManufacturerSpecificData(0xFFFF);
            event_number = bytes[5];
            tx_power_level = bytes[6];
            protocol = bytes[7];
            team_ID = bytes[8];
            player_ID = bytes[9];
            health = (bytes[11] << 8) | bytes[10];
            maximum_health = (bytes[13] << 8) | bytes[12];
            primary_color = (int) ((((long) (bytes[17] & 0xFF)) << 24) | ((bytes[16] & 0xFF) << 16) | ((bytes[15] & 0xFF) << 8) | (bytes[14] & 0xFF));
            secondary_color = (int) ((((long) (bytes[21] & 0xFF)) << 24) | ((bytes[20] & 0xFF) << 16) | ((bytes[19] & 0xFF) << 8) | (bytes[18] & 0xFF));
            SystemK_top_level_state = bytes[22] & 0xFF;
            unused = new byte[4];
            unused[0] = bytes[23];
            unused[1] = bytes[24];
            unused[2] = bytes[25];
            unused[3] = bytes[26];
        }

        public byte[] getBD_ADDR() {
            return BD_ADDR;
        }

        public int getHealth() {
            return health;
        }

        public int getPrimary_color() {
            return primary_color;
        }

        public byte getTeam_ID() { return team_ID; }

        public int getMaximum_health() { return maximum_health; }

        public int getSystemK_top_level_state() { return SystemK_top_level_state; }
    }

    public static class Tag {
        byte[] BD_ADDR;
        int RSSI;
        int event_number;
        int tx_power_level;
        int protocol;
        byte team_ID;
        int player_ID;
        int damage;
        int color;
        byte[] target_BD_ADDR;
        byte[] unused;

        static byte TagEventNumber = 0;
        public Tag(byte[] target_BD_ADDR) {
            this.target_BD_ADDR = new byte[6];
            for(int i = 0; i < 6; i++)
            {
                this.target_BD_ADDR[i] = target_BD_ADDR[5-i];
                //this.target_BD_ADDR[i] = (byte) 0xFF;
            }
        }

        public Tag(ScanResult packet) {
            BD_ADDR = hexStringToByteArray(packet.getDevice().getAddress(), ':');
            RSSI = packet.getRssi();

            byte[] bytes = packet.getScanRecord().getManufacturerSpecificData(0xFFFF);
            event_number = bytes[5];
            tx_power_level = bytes[6];
            protocol = bytes[7];
            team_ID = bytes[8];
            player_ID = bytes[9];
            damage = ((bytes[11] & 0xFF) << 8) | (bytes[10] & 0xFF);
            color = (int) ((((long) (bytes[15] & 0xFF)) << 24) | ((bytes[14] & 0xFF) << 16) | ((bytes[13] & 0xFF) << 8) | (bytes[12] & 0xFF));
            target_BD_ADDR = new byte[6];
            for (int i = 0; i < 6; i++) {
                target_BD_ADDR[i] = bytes[16 + i];
            }
        }

        public byte[] GetBytes()
        {
            byte[] DataBytes = {(byte) 'K',
                    (byte) 'T',
                    (byte) 'a',
                    (byte) 'g',
                    (byte) 0x03,
                    (byte) TagEventNumber++,
                    (byte) 4, // Tx Power Level (dBm)
                    (byte) 0x03, // Protocol
                    (byte) 0x00, // Team ID
                    (byte) 0xFF, // Player ID
                    (byte) 0x9C, // Damage (lsb)
                    (byte) 0xFF, // Damage (msb)
                    (byte) 0xFF, // Primary Color RED
                    (byte) 0xFF, // Primary Color GREEN
                    (byte) 0xFF, // Primary Color BLUE
                    (byte) 0xFE, // Primary Color BRIGHTNESS
                    (byte) target_BD_ADDR[0],
                    (byte) target_BD_ADDR[1],
                    (byte) target_BD_ADDR[2],
                    (byte) target_BD_ADDR[3],
                    (byte) target_BD_ADDR[4],
                    (byte) target_BD_ADDR[5],
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF,
                    (byte) 0xFF};

            return DataBytes;
        }

        public byte[] getBD_ADDR() { return BD_ADDR; }
        public byte getTeam_ID() { return team_ID; }
        public int getPlayer_ID() { return player_ID; }
        public int getDamage() { return damage; }
        public int getColor() { return color; }
        public byte[] getTarget_BD_ADDR() { return target_BD_ADDR; }
    }

    public static class Console {
        byte[] BD_ADDR;
        int RSSI;
        int event_number;
        byte[] console_data;

        public Console(ScanResult packet) {
            BD_ADDR = hexStringToByteArray(packet.getDevice().getAddress(), ':');
            RSSI = packet.getRssi();

            byte[] bytes = packet.getScanRecord().getManufacturerSpecificData(0xFFFF);
            event_number = bytes[5];
            console_data = new byte[21];
            System.arraycopy(bytes, 6, console_data, 0, 21);
        }

        public byte[] getBD_ADDR() { return BD_ADDR; }
        public byte[] getConsole_data() { return console_data; }
    }

    public static class Parameters {
        byte[] BD_ADDR;
        int RSSI;
        int event_number;
        byte[] target_BD_ADDR;
        int subtype;
        int key1;
        int value1;
        int key2;
        int value2;

        static byte ParametersEventNumber = 0;

        public Parameters(byte[] target_BD_ADDR, int subtype, int key1, int value1, int key2, int value2) {
            this.target_BD_ADDR = new byte[6];
            for (int i = 0; i < 6; i++) {
                this.target_BD_ADDR[i] = target_BD_ADDR[5 - i];
            }
            this.subtype = subtype;
            this.key1 = key1;
            this.value1 = value1;
            this.key2 = key2;
            this.value2 = value2;
        }

        public Parameters(ScanResult packet) {
            BD_ADDR = hexStringToByteArray(packet.getDevice().getAddress(), ':');
            RSSI = packet.getRssi();

            byte[] bytes = packet.getScanRecord().getManufacturerSpecificData(0xFFFF);
            event_number = bytes[5];
            target_BD_ADDR = new byte[6];
            for (int i = 0; i < 6; i++) {
                target_BD_ADDR[i] = bytes[6 + i];
            }
            subtype = bytes[12] & 0xFF;
            key1 = ((bytes[14] & 0xFF) << 8) | (bytes[13] & 0xFF);
            value1 = ((bytes[18] & 0xFF) << 24) | ((bytes[17] & 0xFF) << 16) | ((bytes[16] & 0xFF) << 8) | (bytes[15] & 0xFF);
            key2 = ((bytes[20] & 0xFF) << 8) | (bytes[19] & 0xFF);
            value2 = ((bytes[24] & 0xFF) << 24) | ((bytes[23] & 0xFF) << 16) | ((bytes[22] & 0xFF) << 8) | (bytes[21] & 0xFF);
        }

        public byte[] GetBytes() {
            byte[] DataBytes = {(byte) 'K',
                    (byte) 'T',
                    (byte) 'a',
                    (byte) 'g',
                    (byte) PACKET_TYPE_PARAMETERS,
                    (byte) ParametersEventNumber++,
                    (byte) target_BD_ADDR[0],
                    (byte) target_BD_ADDR[1],
                    (byte) target_BD_ADDR[2],
                    (byte) target_BD_ADDR[3],
                    (byte) target_BD_ADDR[4],
                    (byte) target_BD_ADDR[5],
                    (byte) subtype,
                    (byte) ((key1 >> 0) & 0xFF),
                    (byte) ((key1 >> 8) & 0xFF),
                    (byte) ((value1 >> 0) & 0xFF),
                    (byte) ((value1 >> 8) & 0xFF),
                    (byte) ((value1 >> 16) & 0xFF),
                    (byte) ((value1 >> 24) & 0xFF),
                    (byte) ((key2 >> 0) & 0xFF),
                    (byte) ((key2 >> 8) & 0xFF),
                    (byte) ((value2 >> 0) & 0xFF),
                    (byte) ((value2 >> 8) & 0xFF),
                    (byte) ((value2 >> 16) & 0xFF),
                    (byte) ((value2 >> 24) & 0xFF),
                    (byte) 0xFF,
                    (byte) 0xFF};

            return DataBytes;
        }

        public byte[] getTarget_BD_ADDR() {
            return target_BD_ADDR;
        }

        public int getSubtype() {
            return subtype;
        }

        public int getKey1() {
            return key1;
        }

        public int getValue1() {
            return value1;
        }

        public int getKey2() {
            return key2;
        }

        public int getValue2() {
            return value2;
        }
    }

    public static class Hello {
        byte[] BD_ADDR;
        int RSSI;
        int event_number;
        int SystemK_major_version;
        int SystemK_minor_version;
        int device_type;
        byte team_ID;
        String device_name;

        static byte HelloEventNumber = 0;

        public Hello(int major_version, int minor_version, int device_type, byte team_ID, String device_name) {
            this.SystemK_major_version = major_version;
            this.SystemK_minor_version = minor_version;
            this.device_type = device_type;
            this.team_ID = team_ID;
            this.device_name = device_name;
        }

        public Hello(ScanResult packet) {
            BD_ADDR = hexStringToByteArray(packet.getDevice().getAddress(), ':');
            RSSI = packet.getRssi();

            byte[] bytes = packet.getScanRecord().getManufacturerSpecificData(0xFFFF);
            event_number = bytes[5];
            SystemK_major_version = bytes[6] & 0xFF;
            SystemK_minor_version = bytes[7] & 0xFF;
            device_type = ((bytes[9] & 0xFF) << 8) | (bytes[8] & 0xFF);
            team_ID = bytes[10];

            // Extract device name (bytes 11-26, null-terminated string)
            StringBuilder sb = new StringBuilder();
            for (int i = 11; i < bytes.length && bytes[i] != 0; i++) {
                sb.append((char) bytes[i]);
            }
            device_name = sb.toString();
        }

        public byte[] GetBytes() {
            byte[] nameBytes = device_name.getBytes();
            byte[] DataBytes = new byte[27];

            DataBytes[0] = (byte) 'K';
            DataBytes[1] = (byte) 'T';
            DataBytes[2] = (byte) 'a';
            DataBytes[3] = (byte) 'g';
            DataBytes[4] = (byte) PACKET_TYPE_HELLO;
            DataBytes[5] = (byte) HelloEventNumber++;
            DataBytes[6] = (byte) SystemK_major_version;
            DataBytes[7] = (byte) SystemK_minor_version;
            DataBytes[8] = (byte) ((device_type >> 0) & 0xFF);
            DataBytes[9] = (byte) ((device_type >> 8) & 0xFF);
            DataBytes[10] = team_ID;

            // Copy device name (up to 16 bytes)
            int nameLen = Math.min(nameBytes.length, 16);
            for (int i = 0; i < nameLen; i++) {
                DataBytes[11 + i] = nameBytes[i];
            }
            // Fill remaining with 0xFF
            for (int i = nameLen; i < 16; i++) {
                DataBytes[11 + i] = (byte) 0xFF;
            }

            return DataBytes;
        }

        public byte[] getBD_ADDR() {
            return BD_ADDR;
        }

        public int getSystemK_major_version() {
            return SystemK_major_version;
        }

        public int getSystemK_minor_version() {
            return SystemK_minor_version;
        }

        public int getDevice_type() {
            return device_type;
        }

        public byte getTeam_ID() {
            return team_ID;
        }

        public String getDevice_name() {
            return device_name;
        }
    }
}
