import base64
import codecs
import collections
import datetime
import decimal
import doctest
import enum
import json
import sys
import typing
from typing import List, Dict
from dataclasses import dataclass, field


class MultiPopDeque(collections.deque):
    def __init__(self, bs):
        super().__init__([bytes([i]) for i in bs])

    def popn(self, n):
        result = []
        for _ in range(n):
            result.append(self.popleft())

        return b"".join(result)


@dataclass
class UDPChannelHeader():
    length: int = 0
    packet_id: bytes = b""

    @classmethod
    def parse(cls, q):
        o = cls()

        o.length = int.from_bytes(q.popn(2), "big")
        o.packet_id = q.popn(2)
        assert q.popn(1) == bytes([0x01]) # "Not Usable Byte"

        return o


@dataclass
class AVLPacketHeader():
    avl_packet_id: bytes = b""
    imei: bytes = b""

    @classmethod
    def parse(cls, q):
        o = cls()

        o.avl_packet_id = int.from_bytes(q.popn(1), "big")
        imei_length = int.from_bytes(q.popn(2), "big")
        o.imei = q.popn(imei_length)

        return o


class AVLPriority(enum.Enum):
    LOW = 0
    HIGH = 1
    PANIC = 2


class AVLParameter(enum.Enum):
    DIGITAL_INPUT_1 = 1
    DIGITAL_INPUT_2 = 2
    DIGITAL_INPUT_3 = 3
    PULSE_COUNTER_DIN1 = 4
    PULSE_COUNTER_DIN2 = 5
    ANALOG_INPUT_2 = 6
    AUTHORIZED_IBUTTON = 8
    ANALOG_INPUT_1 = 9
    SD_STATUS = 10
    ICCID1 = 11
    FUEL_USED_GPS = 12
    FUEL_RATE_GPS = 13
    ICCID2 = 14
    ECO_SCORE = 15
    TOTAL_ODOMETER = 16
    AXIS_X = 17
    AXIS_Y = 18
    AXIS_Z = 19
    BLE_BATTERY_2 = 20
    GSM_SIGNAL = 21
    BLE_BATTERY_3 = 22
    BLE_BATTERY_4 = 23
    SPEED = 24
    BLE_TEMPERATURE_1 = 25
    BLE_TEMPERATURE_2 = 26
    BLE_TEMPERATURE_3 = 27
    BLE_TEMPERATURE_4 = 28
    BLE_BATTERY_1 = 29
    NUMBER_OF_DTC = 30
    ENGINE_LOAD = 31
    COOLANT_TEMPERATURE = 32
    SHORT_FUEL_TRIM = 33
    FUEL_PRESSURE = 34
    INTAKE_MAP = 35
    ENGINE_RPM = 36
    VEHICLE_SPEED = 37
    TIMING_ADVANCE = 38
    INTAKE_AIR_TEMPERATURE = 39
    MAF = 40
    THROTTLE_POSITION = 41
    RUNTIME_SINCE_ENGINE_START = 42
    DISTANCE_TRAVELED_MIL_ON = 43
    RELATIVE_FUEL_RAIL_PRESSURE = 44
    DIRECT_FUEL_RAIL_PRESSURE = 45
    COMMANDED_EGR = 46
    EGR_ERROR = 47
    FUEL_LEVEL = 48
    DISTANCE_SINCE_CODES_CLEAR = 49
    BAROMETIC_PRESSURE = 50
    CONTROL_MODULE_VOLTAGE = 51
    ABSOLUTE_LOAD_VALUE = 52
    AMBIENT_AIR_TEMPERATURE = 53
    TIME_RUN_WITH_MIL_ON = 54
    TIME_SINCE_CODES_CLEARED = 55
    ABSOLUTE_FUEL_RAIL_PRESSURE = 56
    HYBRID_BATTERY_PACK_LIFE = 57
    ENGINE_OIL_TEMPERATURE = 58
    FUEL_INJECTION_TIMING = 59
    FUEL_RATE = 60
    GEOFENCE_ZONE_06 = 61
    GEOFENCE_ZONE_07 = 62
    GEOFENCE_ZONE_08 = 63
    GEOFENCE_ZONE_09 = 64
    GEOFENCE_ZONE_10 = 65
    EXTERNAL_VOLTAGE = 66
    BATTERY_VOLTAGE = 67
    BATTERY_CURRENT = 68
    GNSS_STATUS = 69
    GEOFENCE_ZONE_11 = 70
    DALLAS_TEMPERATURE_ID_4 = 71
    DALLAS_TEMPERATURE_1 = 72
    DALLAS_TEMPERATURE_2 = 73
    DALLAS_TEMPERATURE_3 = 74
    DALLAS_TEMPERATURE_4 = 75
    DALLAS_TEMPERATURE_ID_1 = 76
    DALLAS_TEMPERATURE_ID_2 = 77
    IBUTTON = 78
    DALLAS_TEMPERATURE_ID_3 = 79
    DATA_MODE = 80
    VEHICLE_SPEED_CAN = 81
    ACCELERATOR_PEDAL_POSITION = 82
    FUEL_CONSUMED = 83
    FUEL_LEVEL_LITERS = 84
    ENGINE_RPM_CAN = 85
    BLE_HUMIDITY_1 = 86
    TOTAL_MILEAGE = 87
    GEOFENCE_ZONE_12 = 88
    FUEL_LEVEL_PERCENT = 89
    DOOR_STATUS = 90
    GEOFENCE_ZONE_13 = 91
    GEOFENCE_ZONE_14 = 92
    GEOFENCE_ZONE_15 = 93
    GEOFENCE_ZONE_16 = 94
    GEOFENCE_ZONE_17 = 95
    GEOFENCE_ZONE_18 = 96
    GEOFENCE_ZONE_19 = 97
    GEOFENCE_ZONE_20 = 98
    GEOFENCE_ZONE_21 = 99
    PROGRAM_NUMBER = 100
    MODULE_ID_8B = 101
    ENGINE_WORKTIME = 102
    ENGINE_WORKTIME_COUNTED = 103
    BLE_HUMIDITY_2 = 104
    TOTAL_MILEAGE_COUNTED = 105
    BLE_HUMIDITY_3 = 106
    FUEL_CONSUMED_COUNTED = 107
    BLE_HUMIDITY_4 = 108
    FUEL_RATE_CAN = 110
    ADBLUE_LEVEL_PERCENT = 111
    ADBLUE_LEVEL = 112
    BATTERY_LEVEL = 113
    ENGINE_LOAD_CAN = 114
    ENGINE_TEMPERATURE = 115
    AXLE_1_LOAD = 118
    AXLE_2_LOAD = 119
    AXLE_3_LOAD = 120
    AXLE_4_LOAD = 121
    AXLE_5_LOAD = 122
    CONTROL_STATE_FLAGS = 123
    AGRICULTURAL_MACHINERY_FLAGS = 124
    HARVESTING_TIME = 125
    AREA_OF_HARVEST = 126
    MOWING_EFFICIENCY = 127
    GRAIN_MOWN_VOLUME = 128
    GRAIN_MOISTURE = 129
    HARVESTING_DRUM_RPM = 130
    GAP_UNDER_HARVESTING_DRUM = 131
    SECURITY_STATE_FLAGS = 132
    TACHOGRAPH_TOTAL_VEHICLE_DISTANCE = 133
    TRIP_DISTANCE = 134
    TACHOGRAPH_VEHICLE_SPEED = 135
    TACHO_DRIVER_CARD_PRESENCE = 136
    DRIVER_1_STATES = 137
    DRIVER_2_STATES = 138
    DRIVER_1_CONTINUOUS_DRIVING_TIME = 139
    DRIVER_2_CONTINUOUS_DRIVING_TIME = 140
    DRIVER_1_CUMULATIVE_BREAK_TIME = 141
    DRIVER_2_CUMULATIVE_BREAK_TIME = 142
    DRIVER_1_SELECTED_ACTIVITY_DURATION = 143
    DRIVER_2_SELECTED_ACTIVITY_DURATION = 144
    DRIVER_1_CUMULATIVE_DRIVING_TIME = 145
    DRIVER_2_CUMULATIVE_DRIVING_TIME = 146
    DRIVER_1_ID_HIGH = 147
    DRIVER_1_ID_LOW = 148
    DRIVER_2_ID_HIGH = 149
    DRIVER_2_ID_LOW = 150
    BATTERY_TEMPERATURE = 151
    BATTERY_LEVEL_CAN = 152
    GEOFENCE_ZONE_22 = 153
    GEOFENCE_ZONE_23 = 154
    GEOFENCE_ZONE_01 = 155
    GEOFENCE_ZONE_02 = 156
    GEOFENCE_ZONE_03 = 157
    GEOFENCE_ZONE_04 = 158
    GEOFENCE_ZONE_05 = 159
    DTC_FAULTS = 160
    SLOPE_OF_ARM = 161
    ROTATION_OF_ARM = 162
    EJECT_OF_ARM = 163
    HORIZONTAL_DISTANCE_ARM_VEHICLE = 164
    HEIGHT_ARM_ABOVE_GROUND = 165
    DRILL_RPM = 166
    AMOUNT_OF_SPREAD_SALT_SQUARE_METER = 167
    BATTERY_VOLTAGE_CAN = 168
    AMOUNT_OF_SPREAD_FINE_GRAINED_SALT = 169
    AMOUNT_OF_COARSE_GRAINED_SALT = 170
    AMOUNT_OF_SPREAD_DIMIX = 171
    AMOUNT_OF_SPREAD_COARSE_GRAINED_CALCIUM = 172
    AMOUNT_OF_SPREAD_CALCIUM_CHLORIDE = 173
    AMOUNT_OF_SPREAD_SODIUM_CHLORIDE = 174
    AUTO_GEOFENCE = 175
    AMOUNT_OF_SPREAD_MAGNESIUM_CHLORIDE = 176
    AMOUNT_OF_SPREAD_GRAVEL = 177
    AMOUNT_OF_SPREAD_SAND = 178
    DIGITAL_OUTPUT_1 = 179
    DIGITAL_OUTPUT_2 = 180
    GNSS_PDOP = 181
    GNSS_HDOP = 182
    WIDTH_POURING_LEFT = 183
    WIDTH_POURING_RIGHT = 184
    SALT_SPREADER_WORKING_HOURS = 185
    DISTANCE_DURING_SALTING = 186
    LOAD_WEIGHT = 187
    RETARDER_LOAD = 188
    CRUISE_TIME = 189
    GEOFENCE_ZONE_24 = 190
    GEOFENCE_ZONE_25 = 191
    GEOFENCE_ZONE_26 = 192
    GEOFENCE_ZONE_27 = 193
    GEOFENCE_ZONE_28 = 194
    GEOFENCE_ZONE_29 = 195
    GEOFENCE_ZONE_30 = 196
    GEOFENCE_ZONE_31 = 197
    GEOFENCE_ZONE_32 = 198
    TRIP_ODOMETER = 199
    SLEEP_MODE = 200
    LLS_1_FUEL_LEVEL = 201
    LLS_1_TEMPERATURE = 202
    LLS_2_FUEL_LEVEL = 203
    LLS_2_TEMPERATURE = 204
    GSM_CELL_ID = 205
    GSM_AREA_CODE = 206
    RFID = 207
    GEOFENCE_ZONE_33 = 208
    GEOFENCE_ZONE_34 = 209
    LLS_3_FUEL_LEVEL = 210
    LLS_3_TEMPERATURE = 211
    LLS_4_FUEL_LEVEL = 212
    LLS_4_TEMPERATURE = 213
    LLS_5_FUEL_LEVEL = 214
    LLS_5_TEMPERATURE = 215
    GEOFENCE_ZONE_35 = 216
    GEOFENCE_ZONE_36 = 217
    GEOFENCE_ZONE_37 = 218
    GEOFENCE_ZONE_38 = 219
    GEOFENCE_ZONE_39 = 220
    GEOFENCE_ZONE_40 = 221
    GEOFENCE_ZONE_41 = 222
    GEOFENCE_ZONE_42 = 223
    GEOFENCE_ZONE_43 = 224
    GEOFENCE_ZONE_44 = 225
    GEOFENCE_ZONE_45 = 226
    GEOFENCE_ZONE_46 = 227
    GEOFENCE_ZONE_47 = 228
    GEOFENCE_ZONE_48 = 229
    GEOFENCE_ZONE_49 = 230
    GEOFENCE_ZONE_50 = 231
    CNG_STATUS = 232
    CNG_USED = 233
    CNG_LEVEL = 234
    OIL_LEVEL = 235
    ALARM = 236
    NETWORK_TYPE = 237
    USER_ID = 238
    IGNITION = 239
    MOVEMENT = 240
    ACTIVE_GSM_OPERATOR = 241
    GREEN_DRIVING_EVENT_DURATION = 243
    TOWING = 246
    CRASH_DETECTION = 247
    IMMOBILIZER = 248
    JAMMING = 249
    TRIP = 250
    IDLING = 251
    UNPLUG = 252
    GREEN_DRIVING_TYPE = 253
    GREEN_DRIVING_VALUE = 254
    OVER_SPEEDING = 255
    VIN = 256
    CRASH_TRACE_DATA = 257
    ECOMAXIMUM = 258
    ECOAVERAGE = 259
    ECODURATION = 260
    BT_STATUS = 263
    BARCODE_ID = 264
    ESCORT_LLS_TEMPERATURE_1 = 269
    BLE_FUEL_LEVEL_1 = 270
    ESCORT_LLS_FUEL_LEVEL_1 = 270
    ESCORT_LLS_BATTERY_VOLTAGE_1 = 271
    ESCORT_LLS_TEMPERATURE_2 = 272
    BLE_FUEL_LEVEL_2 = 273
    ESCORT_LLS_FUEL_LEVEL_2 = 273
    ESCORT_LLS_BATTERY_VOLTAGE_2 = 274
    ESCORT_LLS_TEMPERATURE_3 = 275
    BLE_FUEL_LEVEL_3 = 276
    ESCORT_LLS_FUEL_LEVEL_3 = 276
    ESCORT_LLS_BATTERY_VOLTAGE_3 = 277
    ESCORT_LLS_TEMPERATURE_4 = 278
    BLE_FUEL_LEVEL_4 = 279
    ESCORT_LLS_FUEL_LEVEL_4 = 279
    ESCORT_LLS_BATTERY_VOLTAGE_4 = 280
    FAULT_CODES = 281
    FAULT_CODES_CAN = 282
    DRIVING_STATE = 283
    DRIVING_RECORDS = 284
    BLOOD_ALCOHOL_CONTENT = 285
    INSTANT_MOVEMENT = 303
    VEHICLES_RANGE_ON_BATTERY = 304
    VEHICLES_RANGE_ON_ADDITIONAL_FUEL = 305
    BLE_FUEL_FREQUENCY_1 = 306
    BLE_FUEL_FREQUENCY_2 = 307
    BLE_FUEL_FREQUENCY_3 = 308
    BLE_FUEL_FREQUENCY_4 = 309
    CRASH_EVENT_COUNTER = 317
    VIN_CAN = 325
    UL202_02_SENSOR_FUEL_LEVEL = 327
    AIN_SPEED = 329
    BLE_1_CUSTOM_1 = 331
    BLE_2_CUSTOM_1 = 332
    BLE_3_CUSTOM_1 = 333
    BLE_4_CUSTOM_1 = 334
    BLE_LUMINOSITY_1 = 335
    BLE_LUMINOSITY_2 = 336
    BLE_LUMINOSITY_3 = 337
    BLE_LUMINOSITY_4 = 338
    DIGITAL_OUTPUT_3 = 380
    GROUND_SENSE = 381
    BEACON = 385
    ISO6709_COORDINATES = 387
    MODULE_ID_17B = 388
    OBD_OEM_TOTAL_MILEAGE = 389
    OBD_OEM_FUEL_LEVEL = 390
    PRIVATE_MODE = 391
    DRIVER_NAME = 403
    DRIVER_CARD_LICENSE_TYPE = 404
    DRIVER_GENDER = 405
    DRIVER_CARD_ID = 406
    DRIVER_CARD_EXPIRATION_DATE = 407
    DRIVER_CARD_PLACE_OF_ISSUE = 408
    DRIVER_STATUS_EVENT = 409
    OEM_BATTERY_CHARGE_STATE = 410
    OEM_BATTERY_CHARGE_LEVEL = 411
    OEM_BATTERY_POWER_CONSUMPTION = 412
    IGNITION_ON_COUNTER = 449
    BLE_1_CUSTOM_2 = 463
    BLE_1_CUSTOM_3 = 464
    BLE_1_CUSTOM_4 = 465
    BLE_1_CUSTOM_5 = 466
    BLE_2_CUSTOM_2 = 467
    BLE_2_CUSTOM_3 = 468
    BLE_2_CUSTOM_4 = 469
    BLE_2_CUSTOM_5 = 470
    BLE_3_CUSTOM_2 = 471
    BLE_3_CUSTOM_3 = 472
    BLE_3_CUSTOM_4 = 473
    BLE_3_CUSTOM_5 = 474
    BLE_4_CUSTOM_2 = 475
    BLE_4_CUSTOM_3 = 476
    BLE_4_CUSTOM_4 = 477
    BLE_4_CUSTOM_5 = 478
    UL202_02_SENSOR_STATUS = 483
    MSP500_VENDOR_NAME = 500
    MSP500_VEHICLE_NUMBER = 501
    MSP500_SPEED_SENSOR = 502
    SECURITY_STATE_FLAGS_P4 = 517
    CONTROL_STATE_FLAGS_P4 = 518
    INDICATOR_STATE_FLAGS_P4 = 519
    AGRICULTURAL_STATE_FLAGS_P4 = 520
    UTILITY_STATE_FLAGS_P4 = 521
    CISTERN_STATE_FLAGS_P4 = 522
    HYBRID_SYSTEM_VOLTAGE = 543
    HYBRID_SYSTEM_CURRENT = 544
    UMTS_LTE_CELL_ID = 636
    WAKE_REASON = 637
    OEM_REMAINING_DISTANCE = 755
    FUEL_TYPE = 759
    LNG_USED = 855
    LNG_USED_COUNTED = 856
    LNG_LEVEL_PERCENT = 857
    LNG_LEVEL_KG = 858
    TOTAL_LPG_USED = 1100
    TOTAL_LPG_USED_COUNTED = 1101
    LPG_LEVEL_PROC = 1102
    LPG_LEVEL_LITERS = 1103


@dataclass
class GPSElement():
    longitude: decimal.Decimal = 0
    latitude: decimal.Decimal = 0
    altitude: int = 0
    angle: int = 0
    satellites: int = 0
    speed: int = 0

    @staticmethod
    def _parse_pos(b):
        return decimal.Decimal(
            int.from_bytes(b, "big", signed=True)) / (10 ** 7)

    @classmethod
    def parse(cls, q):
        o = cls()

        o.longitude = cls._parse_pos(q.popn(4))
        o.latitude = cls._parse_pos(q.popn(4))
        o.altitude = int.from_bytes(q.popn(2), "big")
        o.angle = int.from_bytes(q.popn(2), "big")
        o.satellites = int.from_bytes(q.popn(1), "big")
        o.speed = int.from_bytes(q.popn(2), "big")

        return o


@dataclass
class IOElement():
    event_io_id: int = 0
    data: Dict[AVLParameter, int] = field(default_factory=dict)

    def _do_parse_n_bytes(self, n_bytes, q):
        n = int.from_bytes(q.popn(1), "big")

        for _i in range(n):
            io_id = int.from_bytes(q.popn(1), "big")
            io_value = int.from_bytes(q.popn(n_bytes), "big")

            self.data[AVLParameter(io_id)] = io_value

        return n

    @classmethod
    def parse(cls, q):
        o = cls()

        o.event_io_id = int.from_bytes(q.popn(1), "big")
        to_parse = int.from_bytes(q.popn(1), "big")

        to_parse -= o._do_parse_n_bytes(1, q)
        to_parse -= o._do_parse_n_bytes(2, q)
        to_parse -= o._do_parse_n_bytes(4, q)
        to_parse -= o._do_parse_n_bytes(8, q)

        assert to_parse == 0

        return o


@dataclass
class AVLData():
    timestamp: datetime.datetime = datetime.datetime(1970, 1, 1, 0, 0, 0)
    priority: int = 0
    gps_element: GPSElement = None
    io_element: IOElement = None

    @classmethod
    def parse(cls, q):
        o = cls()

        o.timestamp += datetime.timedelta(
            milliseconds=int.from_bytes(q.popn(8), "big"))

        o.priority = AVLPriority(int.from_bytes(q.popn(1), "big"))

        o.gps_element = GPSElement.parse(q)
        o.io_element = IOElement.parse(q)

        return o


@dataclass
class AVLDataArray():
    codec_id: int = 0
    n_records: int = 0
    data: typing.List[AVLData] = field(default_factory=list)

    @classmethod
    def parse(cls, q):
        o = cls()

        o.codec_id = int.from_bytes(q.popn(1), "big")
        o.n_records = int.from_bytes(q.popn(1), "big")

        for _record in range(o.n_records):
            o.data.append(AVLData.parse(q))

        assert o.codec_id == 0x08

        return o


@dataclass
class UDPEncappedPacket():
    udp_channel_header: UDPChannelHeader = None
    avl_packet_header: AVLPacketHeader = None
    avl_data: AVLDataArray = None

    @classmethod
    def parse(cls, q):
        o = cls()

        o.udp_channel_header = UDPChannelHeader.parse(q)
        o.avl_packet_header = AVLPacketHeader.parse(q)
        o.avl_data = AVLDataArray.parse(q)

        return o

    @classmethod
    def from_bytes(cls, bs):
        return cls.parse(MultiPopDeque(bs))


def encode(obj):
    if isinstance(obj, bytes):
        return {"hex": codecs.encode(obj, "hex").decode()}

    elif isinstance(obj, enum.Enum):
        return obj.name

    elif isinstance(obj, (int, str)):
        return obj

    elif isinstance(obj, list):
        return [encode(i) for i in obj]

    elif isinstance(obj, datetime.datetime):
        return obj.isoformat()

    elif isinstance(obj, decimal.Decimal):
        return str(obj)

    elif isinstance(obj, dict):
        result = {}
        for key, value in obj.items():
            result[encode(key)] = encode(value)

        return result

    return encode(obj.__dict__)


#thing = codecs.decode("01a6cafe0105000f3836303634303035303137363130380806000001853c4d315000cb78a26a123a692c000a00000a0000000c05ef00f0001500c800450105b50006b60004422fac43000044000002f100000000100000000000000001853c841fd000cb78a26a123a692c000400000c0000000c05ef00f0001500c800450105b50005b60004422fb243000044000002f100000000100000000000000001853caf238802cb78a26a123a692c000800000d0000fc0d06ef00f0001500c8004501fc0005b50005b60004422fac43000044000002f100000000100000000000000001853cb2251000cb789717123a613a000801000a0000f00d06ef00f0011500c8004501fc0005b50006b60004422fac43000044000002f100000000100000000000000001853cf02fe000cb789885123a66700013012b0b0000000d06ef00f0001504c8004501fc0005b50006b60004422faa430fa044000002f10004bc8a100000000000000001853d271e6000cb789885123a66700010012b0d0000000d06ef00f0001504c8004501fc0005b50005b60003422fbe430f9b44000002f10004bc8a10000000000006", "hex")
thing = base64.b64decode(sys.argv[1])
#thing = sys.stdin.buffer.read()
pkt = UDPEncappedPacket.from_bytes(thing)

print(json.dumps(encode(pkt)))
