#!/usr/bin/env python3

from __future__ import annotations

import asyncio
import logging
import struct
from ctypes import *
from typing import *

from bleak import *

KONASHI_ADV_SERVICE_UUID = "064d0100-8251-49d9-b6f3-f7ba35e5d0a1"

KONASHI_UUID_CONFIG_CMD = "064d0201-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_CFG_CMD_GPIO = 0x01
KONASHI_CFG_CMD_SOFTPWM = 0x02
KONASHI_CFG_CMD_HARDPWM = 0x03
KONASHI_CFG_CMD_ANALOG = 0x04
KONASHI_CFG_CMD_I2C = 0x05
KONASHI_CFG_CMD_UART = 0x06
KONASHI_CFG_CMD_SPI = 0x07
KONASHI_UUID_GPIO_CONFIG_GET = "064d0202-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_SOFTPWM_CONFIG_GET = "064d0203-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_HARDPWM_CONFIG_GET = "064d0204-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_ANALOG_CONFIG_GET = "064d0205-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_I2C_CONFIG_GET = "064d0206-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_UART_CONFIG_GET = "064d0207-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_SPI_CONFIG_GET = "064d0208-8251-49d9-b6f3-f7ba35e5d0a1"

KONASHI_UUID_CONTROL_CMD = "064d0301-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_CTL_CMD_GPIO= 0x01
KONASHI_CTL_CMD_SOFTPWM = 0x02
KONASHI_CTL_CMD_HARDPWM = 0x03
KONASHI_CTL_CMD_ANALOG = 0x04
KONASHI_CTL_CMD_I2C_DATA = 0x05
KONASHI_CTL_CMD_UART_DATA = 0x06
KONASHI_CTL_CMD_SPI_DATA = 0x07
KONASHI_UUID_GPIO_OUTPUT_GET = "064d0302-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_GPIO_INPUT = "064d0303-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_SOFTPWM_OUTPUT_GET = "064d0304-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_HARDPWM_OUTPUT_GET = "064d0305-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_ANALOG_OUTPUT_GET = "064d0306-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_ANALOG_INPUT = "064d0307-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_I2C_DATA_IN = "064d0308-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_UART_DATA_IN = "064d0309-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_UART_DATA_SEND_DONE = "064d030a-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_SPI_DATA_IN = "064d030b-8251-49d9-b6f3-f7ba35e5d0a1"

KONASHI_UUID_BUILTIN = "064d0400-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_BUILTIN_TEMPERATURE = "00002a6e-0000-1000-8000-00805f9b34fb"
KONASHI_UUID_BUILTIN_HUMIDITY = "00002a6f-0000-1000-8000-00805f9b34fb"
KONASHI_UUID_BUILTIN_PRESSURE = "00002a6d-0000-1000-8000-00805f9b34fb"
KONASHI_UUID_BUILTIN_PRESENCE = "00002ae2-0000-1000-8000-00805f9b34fb"
KONASHI_UUID_BUILTIN_ACCELGYRO = "064d0401-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_BUILTIN_RGB_SET = "064d0402-8251-49d9-b6f3-f7ba35e5d0a1"
KONASHI_UUID_BUILTIN_RGB_GET = "064d0403-8251-49d9-b6f3-f7ba35e5d0a1"

KONASHI_GPIO_COUNT = 8
KONASHI_GPIO_WIRED_FCT_NOT_USED = 0
KONASHI_GPIO_WIRED_FCT_AND = 1
KONASHI_GPIO_WIRED_FCT_OR = 2
KONASHI_GPIO_DIRECTION_IN = 0
KONASHI_GPIO_DIRECTION_OUT = 1
class KonashiGpioPinConfig(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('function', c_uint8, 4),
        ('', c_uint8, 4),
        ('pull_down', c_uint8, 1),
        ('pull_up', c_uint8, 1),
        ('wired_fct', c_uint8, 2),
        ('direction', c_uint8, 1),
        ('send_on_change', c_uint8, 1),
        ('', c_uint8, 2)
    ]
    def __init__(self, direction: int=KONASHI_GPIO_DIRECTION_IN, send_on_change: bool=True, pull_down: bool=False, pull_up: bool=False, wired_fct: int=KONASHI_GPIO_WIRED_FCT_NOT_USED):
        """
        direction (int): the pin direction, one of KONASHI_GPIO_DIRECTION_IN or KONASHI_GPIO_DIRECTION_OUT
        send_on_change (bool): if true, a notification is sent on pin level change
        pull_down (bool): if true, activate the pull down resistor
        pull_up (bool): if true, activate the pull up resistor
        wired_function (int): use the pin in a wired function mode, one of KONASHI_GPIO_WIRED_FCT_NOT_USED, KONASHI_GPIO_WIRED_FCT_AND or KONASHI_GPIO_WIRED_FCT_OR
        """
        self.direction = direction
        self.send_on_change = send_on_change
        self.pull_down = pull_down
        self.pull_up = pull_up
        self.wired_fct = wired_fct
_KonashiGpioPinsConfig = KonashiGpioPinConfig*KONASHI_GPIO_COUNT

class KonashiSoftPwmPinConfig(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('control', c_uint8, 4),
        ('', c_uint8, 4),
        ('fixed_value', c_uint16)
    ]

KONASHI_HARDPWM_COUNT = 4
class KonashiHardPwmPinConfig(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('enabled', c_uint8, 1),
        ('', c_uint8, 7)
    ]
class KonashiHardPwmConfig(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('pin', KonashiHardPwmPinConfig*KONASHI_HARDPWM_COUNT),
        ('prescale', c_uint8, 4),
        ('clock', c_uint8, 4),
        ('period', c_uint16)
    ]

KONASHI_AIO_COUNT = 3
class KonashiAnalogPinConfig(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('direction', c_uint8, 1),
        ('send_on_change', c_uint8, 1),
        ('', c_uint8, 1),
        ('enabled', c_uint8, 1),
        ('', c_uint8, 4)
    ]
class KonashiAnalogConfig(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('pin', KonashiAnalogPinConfig*KONASHI_AIO_COUNT),
        ('adc_update_period', c_uint8),
        ('adc_voltage_reference', c_uint8, 4),
        ('', c_uint8, 4),
        ('vdac_voltage_reference', c_uint8, 4),
        ('', c_uint8, 4),
        ('idac_current_step', c_uint8, 4),
        ('', c_uint8, 4)
    ]

class KonashiI2cConfig(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('mode', c_uint8, 1),
        ('enabled', c_uint8, 1),
        ('', c_uint8, 6)
    ]

class KonashiUartConfig(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('stop_bits', c_uint8, 2),
        ('parity', c_uint8, 2),
        ('', c_uint8, 3),
        ('enabled', c_uint8, 1),
        ('baudrate', c_uint32)
    ]

class KonashiSpiConfig(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('mode', c_uint8, 2),
        ('', c_uint8, 1),
        ('endian', c_uint8, 1),
        ('', c_uint8, 3),
        ('enabled', c_uint8, 1),
        ('bitrate', c_uint32)
    ]

KONASHI_GPIO_LEVEL_LOW = 0
KONASHI_GPIO_LEVEL_HIGH = 1
KONASHI_GPIO_LEVEL_TOGGLE = 2
class KonashiGpioPinControl(LittleEndianStructure):
    _pack_ = 1
    _fields_ = [
        ('level', c_uint8, 1),
        ('', c_uint8, 3),
        ('valid', c_uint8, 1),
        ('', c_uint8, 3)
    ]
_KonashiGpioPinsControl = KonashiGpioPinControl*KONASHI_GPIO_COUNT

class NotFoundError(Exception):
    pass

class InvalidDeviceError(Exception):
    pass

class Konashi:
    def __init__(self, name: str) -> None:
        self._name = name
        self._ble_dev = None
        self._ble_client = None
        self._gpio_config = _KonashiGpioPinsConfig()
        self._gpio_output = _KonashiGpioPinsControl()
        self._gpio_input = _KonashiGpioPinsControl()
        self._gpio_input_cb = None
        self._builtin_temperature_cb = None
        self._builtin_humidity_cb = None
        self._builtin_pressure_cb = None
        self._builtin_presence_cb = None
        self._builtin_accelgyro_cb = None
        self._builtin_rgb_transition_end_cb = None

    def __str__(self):
        return f'Konashi {self._name} ({"Unknown" if self._ble_dev is None else self._ble_dev.address})'

    def __repr__(self):
        return f'Konashi(name="{self._name}")'

    def __eq__(self, other):
        if self._ble_dev is not None and other._ble_dev is not None:
            return self._ble_dev.address == other._ble_dev.address
        return self._name == other._name

    def __ne__(self, other):
        return not self.__eq__(other)

    @staticmethod
    async def find(name: str, timeout: float) -> Konashi:
        if not timeout > 0.0:
            raise ValueError("Timeout should be longer than 0 seconds")
        _konashi = None
        _invalid = False
        _scan_task = None
        _scanner = BleakScanner()
        def _scan_cb(dev: BLEDevice, adv: AdvertisementData):
            nonlocal _konashi
            nonlocal _invalid
            if dev.name == name:
                if KONASHI_ADV_SERVICE_UUID in adv.service_uuids:
                    _konashi = Konashi(name)
                    _konashi._ble_dev = dev
                else:
                    _invalid = True
                _scanner.register_detection_callback(None)
                if _scan_task:
                    _scan_task.cancel()
        _scanner.register_detection_callback(_scan_cb)
        _timedout = False
        async def _scan_coro(t: float) -> None:
            nonlocal _timedout
            try:
                await _scanner.start()
                if timeout > 0:
                    await asyncio.sleep(t)
                else:
                    while True:
                        await asyncio.sleep(100)
                _timedout = True
            except asyncio.CancelledError:
                _timedout = False
            finally:
                await _scanner.stop()
        _scan_task = asyncio.create_task(_scan_coro(timeout))
        await _scan_task
        if _timedout:
            raise NotFoundError(f'Could not find {name}')
        elif _invalid:
            raise InvalidDeviceError(f'{name} is not a Konashi device')
        else:
            return _konashi

    @staticmethod
    async def search(timeout: float) -> List[Konashi]:
        if not timeout > 0.0:
            raise ValueError("Timeout should be longer than 0 seconds")
        _konashi = []
        def _scan_cb(dev: BLEDevice, adv: AdvertisementData):
            nonlocal _konashi
            if KONASHI_ADV_SERVICE_UUID in adv.service_uuids:
                k = Konashi(dev.name)
                k._ble_dev = dev
                if k not in _konashi:
                    _konashi.append(k)
        _scanner = BleakScanner()
        _scanner.register_detection_callback(_scan_cb)
        await _scanner.start()
        await asyncio.sleep(timeout)
        _scanner.register_detection_callback(None)
        await _scanner.stop()
        return _konashi

    async def connect(self, timeout: float) -> None:
        if not timeout > 0.0:
            raise ValueError("Timeout should be longer than 0 seconds")
        if self._ble_dev is None:
            try:
                k = await self.find(self._name, timeout)
                self._ble_dev = k._ble_dev
            except NotFoundError:
                raise
            except InvalidDeviceError:
                raise
        if self._ble_client is None:
            self._ble_client = BleakClient(self._ble_dev.address)
        _con = await self._ble_client.connect(timeout=timeout)
        if _con:
            buf = await self._ble_client.read_gatt_char(KONASHI_UUID_GPIO_CONFIG_GET)
            self._gpio_config = _KonashiGpioPinsConfig.from_buffer_copy(buf)
            await self._ble_client.start_notify(KONASHI_UUID_GPIO_CONFIG_GET, self._ntf_cb_gpio_config_get)
            await self._ble_client.start_notify(KONASHI_UUID_SOFTPWM_CONFIG_GET, self._ntf_cb_softpwm_config_get)
            await self._ble_client.start_notify(KONASHI_UUID_HARDPWM_CONFIG_GET, self._ntf_cb_hardpwm_config_get)
            await self._ble_client.start_notify(KONASHI_UUID_ANALOG_CONFIG_GET, self._ntf_cb_analog_config_get)
            await self._ble_client.start_notify(KONASHI_UUID_I2C_CONFIG_GET, self._ntf_cb_i2c_config_get)
            await self._ble_client.start_notify(KONASHI_UUID_UART_CONFIG_GET, self._ntf_cb_uart_config_get)
            await self._ble_client.start_notify(KONASHI_UUID_SPI_CONFIG_GET, self._ntf_cb_spi_config_get)

            buf = await self._ble_client.read_gatt_char(KONASHI_UUID_GPIO_CONFIG_GET)
            self._gpio_output = _KonashiGpioPinsControl.from_buffer_copy(buf)
            await self._ble_client.start_notify(KONASHI_UUID_GPIO_OUTPUT_GET, self._ntf_cb_gpio_output_get)
            buf = await self._ble_client.read_gatt_char(KONASHI_UUID_GPIO_CONFIG_GET)
            self._gpio_input = _KonashiGpioPinsControl.from_buffer_copy(buf)
            await self._ble_client.start_notify(KONASHI_UUID_GPIO_INPUT, self._ntf_cb_gpio_input)
            await self._ble_client.start_notify(KONASHI_UUID_SOFTPWM_OUTPUT_GET, self._ntf_cb_softpwm_output_get)
            await self._ble_client.start_notify(KONASHI_UUID_HARDPWM_OUTPUT_GET, self._ntf_cb_hardpwm_output_get)
            await self._ble_client.start_notify(KONASHI_UUID_ANALOG_OUTPUT_GET, self._ntf_cb_analog_output_get)
            await self._ble_client.start_notify(KONASHI_UUID_ANALOG_INPUT, self._ntf_cb_analog_input)
            await self._ble_client.start_notify(KONASHI_UUID_I2C_DATA_IN, self._ntf_cb_i2c_data_in)
            await self._ble_client.start_notify(KONASHI_UUID_UART_DATA_IN, self._ntf_cb_uart_data_in)
            await self._ble_client.start_notify(KONASHI_UUID_UART_DATA_SEND_DONE, self._ntf_cb_uart_data_send_done)
            await self._ble_client.start_notify(KONASHI_UUID_SPI_DATA_IN, self._ntf_cb_spi_data_in)

            has_builtin = False
            srvcs = await self._ble_client.get_services()
            for s in srvcs:
                if s.uuid == KONASHI_UUID_BUILTIN:
                    has_builtin = True
            if has_builtin:
                print("Konashi has built-in sensors")
                await self._ble_client.start_notify(KONASHI_UUID_BUILTIN_RGB_GET, self._ntf_cb_builtin_rgb_get)

    async def disconnect(self) -> None:
        if self._ble_client is not None:
            await self._ble_client.disconnect()
            self._ble_client = None

    def _ntf_cb_gpio_config_get(self, sender, data):
        self._gpio_config = _KonashiGpioPinsConfig.from_buffer_copy(data)
    def _ntf_cb_softpwm_config_get(self, sender, data):
        pass
    def _ntf_cb_hardpwm_config_get(self, sender, data):
        pass
    def _ntf_cb_analog_config_get(self, sender, data):
        pass
    def _ntf_cb_i2c_config_get(self, sender, data):
        pass
    def _ntf_cb_uart_config_get(self, sender, data):
        pass
    def _ntf_cb_spi_config_get(self, sender, data):
        pass

    def _ntf_cb_gpio_output_get(self, sender, data):
        self._gpio_output = _KonashiGpioPinsControl.from_buffer_copy(data)
    def _ntf_cb_gpio_input(self, sender, data):
        for i in range(KONASHI_GPIO_COUNT):
            if data[i]&0x10:
                val = data[i]&0x01
                if self._gpio_input[i].level != val:
                    if self._gpio_input_cb is not None:
                        self._gpio_input_cb(i, val)
        self._gpio_input = _KonashiGpioPinsControl.from_buffer_copy(data)
    def _ntf_cb_softpwm_output_get(self, sender, data):
        pass
    def _ntf_cb_hardpwm_output_get(self, sender, data):
        pass
    def _ntf_cb_analog_output_get(self, sender, data):
        pass
    def _ntf_cb_analog_input(self, sender, data):
        pass
    def _ntf_cb_i2c_data_in(self, sender, data):
        pass
    def _ntf_cb_uart_data_in(self, sender, data):
        pass
    def _ntf_cb_uart_data_send_done(self, sender, data):
        pass
    def _ntf_cb_spi_data_in(self, sender, data):
        pass

    def _ntf_cb_builtin_temperature(self, sender, data):
        d = struct.unpack("<h", data)
        temp = d[0]
        temp /= 100
        if self._builtin_temperature_cb is not None:
            self._builtin_temperature_cb(temp)
    def _ntf_cb_builtin_humidity(self, sender, data):
        d = struct.unpack("<h", data)
        hum = d[0]
        hum /= 100
        if self._builtin_humidity_cb is not None:
            self._builtin_humidity_cb(hum)
    def _ntf_cb_builtin_pressure(self, sender, data):
        d = struct.unpack("<i", data)
        press = d[0]
        press /= 1000
        if self._builtin_pressure_cb is not None:
            self._builtin_pressure_cb(press)
    def _ntf_cb_builtin_presence(self, sender, data):
        d = struct.unpack("<?", data)
        pres = d[0]
        if self._builtin_presence_cb is not None:
            self._builtin_presence_cb(pres)
    def _ntf_cb_builtin_accelgyro(self, sender, data):
        d = struct.unpack("<hhhhhh", data)
        accel_x = d[0] / 32768 * 8
        accel_y = d[1] / 32768 * 8
        accel_z = d[2] / 32768 * 8
        gyro_x = d[3] / 32768 * 1000
        gyro_y = d[4] / 32768 * 1000
        gyro_z = d[5] / 32768 * 1000
        if self._builtin_accelgyro_cb is not None:
            self._builtin_accelgyro_cb((accel_x,accel_y,accel_z),(gyro_x,gyro_y,gyro_z))
    def _ntf_cb_builtin_rgb_get(self, sender, data):
        d = struct.unpack("<ccccH", data)
        color = (d[0],d[1],d[2],d[3])
        if self._builtin_rgb_transition_end_cb is not None:
            self._builtin_rgb_transition_end_cb(color)
            self._builtin_rgb_transition_end_cb = None

    async def gpioConfigSet(self, configs: Sequence(Tuple[int, bool, KonashiGpioPinConfig])) -> None:
        """
        Specify a list of configurations in the format (pin_bitmask, enable, config) with:
          pin_bitmask (int): a bitmask of the pins to apply this configuration to
          enable (bool): enable or disable the specified pins
          config (KonashiGpioPinConfig): the configuration for the specified pins
        """
        b = bytearray([KONASHI_CFG_CMD_GPIO])
        for config in configs:
            for i in range(KONASHI_GPIO_COUNT):
                if (config[0]&(1<<i)) > 0:
                    b.extend(bytearray([(i<<4)|(0x1 if config[1] else 0x0), bytes(config[2])[1]]))
        await self._ble_client.write_gatt_char(KONASHI_UUID_CONFIG_CMD, b)

    def gpioSetInputCallback(self, notify_callback: Callable[[int, int], None]) -> None:
        """
        The callback is called with parameters:
          pin (int)
          value (int)
        """
        self._gpio_input_cb = notify_callback

    async def gpioControl(self, controls: Sequence(Tuple[int, int])) -> None:
        """
        Specify a list of controls in the format (pin, level) with:
          pin (int): a bitmask of the pins to apply this control to
          level (int): the control for the specified pins
        """
        b = bytearray([KONASHI_CTL_CMD_GPIO])
        for control in controls:
            for i in range(KONASHI_GPIO_COUNT):
                if (control[0]&(1<<i)) > 0:
                    b.extend(bytearray([(i<<4)|(control[1])]))
        await self._ble_client.write_gatt_char(KONASHI_UUID_CONTROL_CMD, b)

    async def builtinSetTemperatureCallback(self, notify_callback: Callable[[float], None]) -> None:
        """
        The callback is called with parameters:
          temperature in degrees Celsius (float)
        """
        if notify_callback is not None:
            self._builtin_temperature_cb = notify_callback
            await self._ble_client.start_notify(KONASHI_UUID_BUILTIN_TEMPERATURE, self._ntf_cb_builtin_temperature)
        else:
            await self._ble_client.stop_notify(KONASHI_UUID_BUILTIN_TEMPERATURE)
            self._builtin_temperature_cb = None

    async def builtinSetHumidityCallback(self, notify_callback: Callable[[float], None]) -> None:
        """
        The callback is called with parameters:
          humidity in percent (float)
        """
        if notify_callback is not None:
            self._builtin_humidity_cb = notify_callback
            await self._ble_client.start_notify(KONASHI_UUID_BUILTIN_HUMIDITY, self._ntf_cb_builtin_humidity)
        else:
            await self._ble_client.stop_notify(KONASHI_UUID_BUILTIN_HUMIDITY)
            self._builtin_humidity_cb = None

    async def builtinSetPressureCallback(self, notify_callback: Callable[[float], None]) -> None:
        """
        The callback is called with parameters:
          pressure in hectopascal (float)
        """
        if notify_callback is not None:
            self._builtin_pressure_cb = notify_callback
            await self._ble_client.start_notify(KONASHI_UUID_BUILTIN_PRESSURE, self._ntf_cb_builtin_pressure)
        else:
            await self._ble_client.stop_notify(KONASHI_UUID_BUILTIN_PRESSURE)
            self._builtin_pressure_cb = None

    async def builtinSetPresenceCallback(self, notify_callback: Callable[[bool], None]) -> None:
        """
        The callback is called with parameters:
          presence (bool)
        """
        if notify_callback is not None:
            self._builtin_presence_cb = notify_callback
            await self._ble_client.start_notify(KONASHI_UUID_BUILTIN_PRESENCE, self._ntf_cb_builtin_presence)
        else:
            await self._ble_client.stop_notify(KONASHI_UUID_BUILTIN_PRESENCE)
            self._builtin_presence_cb = None

    async def builtinSetAccelGyroCallback(self, notify_callback: Callable[[(float,float,float),(float,float,float)], None]) -> None:
        """
        The callback is called with parameters:
          accel in g (Tuple(float,float,float))
          gyro in degrees per second (Tuple(float,float,float))
        """
        if notify_callback is not None:
            self._builtin_accelgyro_cb = notify_callback
            await self._ble_client.start_notify(KONASHI_UUID_BUILTIN_ACCELGYRO, self._ntf_cb_builtin_accelgyro)
        else:
            await self._ble_client.stop_notify(KONASHI_UUID_BUILTIN_ACCELGYRO)
            self._builtin_accelgyro_cb = None

    async def builtinSetRgb(self, r: int, g: int, b: int, a: int, duration: int, transition_end_cb: Callable[[(int,int,int,int)], None] = None) -> None:
        """
        Set the RGB LED color.
        r (int): Red (0~255)
        g (int): Green (0~255)
        b (int): Blue (0~255)
        a (int): Alpha (0~255)
        duration (int): duration to new color in milliseconds (0~65535)
        """
        b = bytearray([r&0xFF, g&0xFF, b&0xFF, a&0xFF, (duration&0x00FF), ((duration&0xFF00)>>8)])
        await self._ble_client.write_gatt_char(KONASHI_UUID_BUILTIN_RGB_SET, b)
        if transition_end_cb is not None:
            self._builtin_rgb_transition_end_cb = transition_end_cb


#! /usr/bin/env python3
import argparse
import subprocess
from os import listdir
from os.path import isfile, join, isdir

def main():
    parser = argparse.ArgumentParser(description="Scrape and analyze Cargo.lock files")
    parser.add_argument(
        "--cargofile", required=False, help="Cargo lockfile location", type=str, nargs='?', metavar=('cargofile'), const='Cargo.lock'
    )
    parser.add_argument(
        "--download", help="Download source tarballs", action="store_true"
    )
    parser.add_argument(
        "--analyze", help="Analyze downloaded source files", action="store_true"
    )
    args = parser.parse_args()
    if args.download == False and args.analyze == False:
        print("At least one of --download or --analyze should be specified. Doing nothing.")

    if args.cargofile == None:
        cargofile = 'Cargo.lock'
    else:
        cargofile = args.cargofile

    if args.download == True:
        with open(cargofile, 'r') as cf:
            lines = cf.readlines()
            curpkg = None
            curver = None
            source = False
            for line in lines:
                if "[[package]]" in line:
                    if curpkg != None and curver != None and source == True:
                        print("Downloading {} {}".format(curpkg, curver))
                        with open('crates/{}-{}.tgz'.format(curpkg, curver), "wb") as of:
                            subprocess.run(['cargo', 'download', '{}=={}'.format(curpkg, curver)], stdout=of)
                    #elif curpkg == None and curver != None:
                    #    print("package missing")
                    #elif curpkg != None and curver == None:
                    #    print("version missing")
                    curpkg = None
                    curver = None
                    source = False
                if "name =" in line:
                    curpkg = line.split('=')[1].rstrip().replace('"', '').strip()
                if "version =" in line:
                    curver = line.split('=')[1].rstrip().replace('"', '').strip()
                if "source =" in line:
                    if "registry" in line:
                        source = True
                    else:
                        curpkg = None
                        curver = None
                        source = False
                        print("WARNING: github source for {} not downloaded".format(curpkg))

    if args.analyze == True:
        # look for build.rs in the source files
        path = "./crates"
        onlyfiles = [f for f in listdir(path) if isfile(join(path, f))]
        for sourcefile in onlyfiles:
            subprocess.run(['tar', '-C', 'builds/', '-xzf', '{}/{}'.format(path, sourcefile), '--wildcards', '--no-anchored', '*build.rs'])
        buildpath = "./builds"
        onlydirs = [f for f in listdir(buildpath) if isdir(join(buildpath, f))]
        # print(onlydirs)
        with open('builds.rs', 'w') as bf:
            for dir in onlydirs:
                try:
                    with open('{}/{}/build.rs'.format(buildpath, dir), 'r') as buildfile:
                        bf.write("========== build.rs from {} ============================================================\n".format(dir))
                        bf.write(buildfile.read())
                except OSError as e:
                        pass

if __name__ == "__main__":
    main()
    exit(0)


import os,json,sys,re,string

class nblearn(object):
    def __init__(self, path):
        self.path=path
        self.vocab={}
        self.stop_words=[]
        self.spamtokencount=0
        self.hamtokencount=0
        self.spamfilecount=0
        self.hamfilecount=0

    def generatemodal(self):
        for root, dirs, files in os.walk(self.path):
            if root.endswith('/spam'):
                self.updatedict(root,files,0)
            elif root.endswith('/ham'):
                self.updatedict(root,files,1)

    def updatedict(self,root,files,sh):
        for curfile in files:
            if curfile.endswith(".txt"):
                if sh:
                    self.hamfilecount+=1
                else:
                    self.spamfilecount+=1
                rfile=open(os.path.join(root, curfile), "r", encoding='latin1')
                for data in rfile:
                    contentlist=data.strip().split()
                    for token in contentlist:
                        token=re.sub('[%s]' % re.escape(string.punctuation), '', token)
                        if token not in self.stop_words:
                            if sh:
                                self.hamtokencount+=1
                            else:
                                self.spamtokencount+=1
                            if token in self.vocab:
                                self.vocab[token][sh]+=1
                            else:
                                if sh:
                                    self.vocab[token]=[0,1]
                                else:
                                    self.vocab[token]=[1,0]

    def writemodal(self):
        with open('nbmodal.txt', 'w') as wfile:
            json.dump({'spamfilecount':self.spamfilecount,'spamcount':self.spamtokencount,'hamfilecount':self.hamfilecount,'hamcount':self.hamtokencount,'data':self.vocab}, wfile)

    def load_stop_words(self):
        with open('stop_words.txt') as data_file:
            data = json.load(data_file)
        self.stop_words=data['stop_words']

def main():
    mydir = sys.argv[1]
    learnmodal=nblearn(mydir)
    learnmodal.load_stop_words()
    learnmodal.generatemodal()
    learnmodal.writemodal()

if __name__ == '__main__':
    main()


from enum import Enum

# ---------------------------------- #
#    NaturalStatTrick data classes   #
# ---------------------------------- #
class nst_sit(Enum):
    ALL_STRENGTHS = 'all'
    EVEN_STRENGTH = 'ev'
    FULL_STRENGTH = '5v5'
    FULL_ADJUSTED = 'sva' # Score and venue adjusted
    POWERPLAY = 'pp'
    FIVE_ON_FOUR_PP = '5v4'
    PENALTYKILL = 'pk'
    FOUR_ON_FIVE_PK = '4v5'
    WITH_EMPTY_NET = 'enf'
    AGAINST_EMPTY_NET = 'ena'

class nst_score(Enum):
    ALL_SCORES = 'all'
    TIED = 'tied'
    LEADING = 'u'
    TRAILING = 'd'
    WITHIN_ONE = 'w1'
    UP_ONE = 'u1'
    DOWN_ONE = 'd1'

class nst_rate(Enum):
    RATES = 'y'
    COUNTS = 'n'

class nst_loc(Enum):
    HOME_AND_AWAY = 'B'
    HOME = 'H'
    AWAY = 'A'

class nst_gpf(Enum):
    FULL_SEASON = '410'
    LAST_TEN = '10'

# ------------------------------- #
#    LeftWingLock data classes    #
# ------------------------------- #
class lwl_strengths(Enum):
    EV = 'EV'
    PP = 'PP'
    PK = 'SH'

class lwl_gametypes(Enum):
    GAMEDAY = 'GD'
    MOST_RECENT = '1'
    LAST_3 = '3'

# -------------------------------- #
#    DraftkingsAPI data classes    #
# -------------------------------- #
CLASSIC_IDS = (1, 72, 122, 98, 70, 94, 125, 21)
SHOWDOWN_IDS = []

# -------------------------- #
#    NHL_API data classes    #
# -------------------------- #

# ---------------- #
#    Odds sites    #
# ---------------- #
US_SITES = ('pointsbetus', 'betonlineag', 'bookmaker', 'mybookieag', 'intertops', 'betfair', 'lowvig')
UK_SITES = ('mybookieag', 'betfair', 'willhill', 'paddypower')
EU_SITES = ('betonlineag', 'bookmaker', 'mybookieag', 'intertops', 'betfair', 'pinnacle')

# ---------------------------------------- #
#    All Team Ids, Names, Abbreviations    #
# ---------------------------------------- #

# --------- #
#    NHL    #
# --------- #
NHL_TEAMS = {
    'NJD': {
     'dk_abbreviation': 'NJ',
     'dk_team_id': 4964,
     'nhl_id': 1,
     'name': 'New Jersey Devils',
     'secondary_name': 'New Jersey Devils',
     'slug': 'new-jersey-devils',
     'nhl_abbreviation': 'NJD',
     'nst_abbreviation': 'N.J',
    },
    'NYI': {
     'dk_abbreviation': 'NYI',
     'dk_team_id': 4965,
     'nhl_id': 2,
     'name': 'New York Islanders',
     'secondary_name': 'New York Islanders',
     'slug': 'new-york-islanders',
     'nhl_abbreviation': 'NYI',
     'nst_abbreviation': 'NYI',
    },
    'NYR': {
     'dk_abbreviation': 'NYR',
     'dk_team_id': 4966,
     'nhl_id': 3,
     'name': 'New York Rangers',
     'secondary_name': 'New York Rangers',
     'slug': 'new-york-rangers',
     'nhl_abbreviation': 'NYR',
     'nst_abbreviation': 'NYR',
    },
    'PHI': {
     'dk_abbreviation': 'PHI',
     'dk_team_id': 4968,
     'nhl_id': 4,
     'name': 'Philadelphia Flyers',
     'secondary_name': 'Philadelphia Flyers',
     'slug': 'philadelphia-flyers',
     'nhl_abbreviation': 'PHI',
     'nst_abbreviation': 'PHI',
    },
    'PIT': {
     'dk_abbreviation': 'PIT',
     'dk_team_id': 4969,
     'nhl_id': 5,
     'name': 'Pittsburgh Penguins',
     'secondary_name': 'Pittsburgh Penguins',
     'slug': 'pittsburgh-penguins',
     'nhl_abbreviation': 'PIT',
     'nst_abbreviation': 'PIT',
    },
    'BOS': {
     'dk_abbreviation': 'BOS',
     'dk_team_id': 4954,
     'nhl_id': 6,
     'name': 'Boston Bruins',
     'secondary_name': 'Boston Bruins',
     'slug': 'boston-bruins',
     'nhl_abbreviation': 'BOS',
     'nst_abbreviation': 'BOS',
    },
    'BUF': {
     'dk_abbreviation': 'BUF',
     'dk_team_id': 4955,
     'nhl_id': 7,
     'name': 'Buffalo Sabres',
     'secondary_name': 'Buffalo Sabres',
     'slug': 'buffalo-sabres',
     'nhl_abbreviation': 'BUF',
     'nst_abbreviation': 'BUF',
    },
    'MTL': {
     'dk_abbreviation': 'MON',
     'dk_team_id': 4963,
     'nhl_id': 8,
     'name': 'Montréal Canadiens',
     'secondary_name': 'Montreal Canadiens',
     'slug': 'montreal-canadiens',
     'nhl_abbreviation': 'MTL',
     'nst_abbreviation': 'MTL',
    },
    'OTT': {
     'dk_abbreviation': 'OTT',
     'dk_team_id': 4967,
     'nhl_id': 9,
     'name': 'Ottawa Senators',
     'secondary_name': 'Ottawa Senators',
     'slug': 'ottawa-senators',
     'nhl_abbreviation': 'OTT',
     'nst_abbreviation': 'OTT',
    },
    'TOR': {
     'dk_abbreviation': 'TOR',
     'dk_team_id': 4974,
     'nhl_id': 10,
     'name': 'Toronto Maple Leafs',
     'secondary_name': 'Toronto Maple Leafs',
     'slug': 'toronto-maple-leafs',
     'nhl_abbreviation': 'TOR',
     'nst_abbreviation': 'TOR',
    },
    'CAR': {
     'dk_abbreviation': 'CAR',
     'dk_team_id': 4960,
     'nhl_id': 12,
     'name': 'Carolina Hurricanes',
     'secondary_name': 'Carolina Hurricanes',
     'slug': 'carolina-hurricanes',
     'nhl_abbreviation': 'CAR',
     'nst_abbreviation': 'CAR',
    },
    'FLA': {
     'dk_abbreviation': 'FLA',
     'dk_team_id': 4979,
     'nhl_id': 13,
     'name': 'Florida Panthers',
     'secondary_name': 'Florida Panthers',
     'slug': 'florida-panthers',
     'nhl_abbreviation': 'FLA',
     'nst_abbreviation': 'FLA',
    },
    'TBL': {
     'dk_abbreviation': 'TB',
     'dk_team_id': 4973,
     'nhl_id': 14,
     'name': 'Tampa Bay Lightning',
     'secondary_name': 'Tampa Bay Lightning',
     'slug': 'tampa-bay-lightning',
     'nhl_abbreviation': 'TBL',
     'nst_abbreviation': 'T.B',
    },
    'WSH': {
     'dk_abbreviation': 'WAS',
     'dk_team_id': 4976,
     'nhl_id': 15,
     'name': 'Washington Capitals',
     'secondary_name': 'Washington Capitals',
     'slug': 'washington-capitals',
     'nhl_abbreviation': 'WSH',
     'nst_abbreviation': 'WSH',
    },
    'CHI': {
     'dk_abbreviation': 'CHI',
     'dk_team_id': 4957,
     'nhl_id': 16,
     'name': 'Chicago Blackhawks',
     'secondary_name': 'Chicago Blackhawks',
     'slug': 'chicago-blackhawks',
     'nhl_abbreviation': 'CHI',
     'nst_abbreviation': 'CHI',
    },
    'DET': {
     'dk_abbreviation': 'DET',
     'dk_team_id': 4958,
     'nhl_id': 17,
     'name': 'Detroit Red Wings',
     'secondary_name': 'Detroit Red Wings',
     'slug': 'detroit-red-wings',
     'nhl_abbreviation': 'DET',
     'nst_abbreviation': 'DET',
    },
    'NSH': {
     'dk_abbreviation': 'NSH',
     'dk_team_id': 4980,
     'nhl_id': 18,
     'name': 'Nashville Predators',
     'secondary_name': 'Nashville Predators',
     'slug': 'nashville-predators',
     'nhl_abbreviation': 'NSH',
     'nst_abbreviation': 'NSH',
    },
    'STL': {
     'dk_abbreviation': 'STL',
     'dk_team_id': 4972,
     'nhl_id': 19,
     'name': 'St. Louis Blues',
     'secondary_name': 'St Louis Blues',
     'slug': 'st-louis-blues',
     'nhl_abbreviation': 'STL',
     'nst_abbreviation': 'STL',
    },
    'CGY': {
     'dk_abbreviation': 'CGY',
     'dk_team_id': 4956,
     'nhl_id': 20,
     'name': 'Calgary Flames',
     'secondary_name': 'Calgary Flames',
     'slug': 'calgary-flames',
     'nhl_abbreviation': 'CGY',
     'nst_abbreviation': 'CGY',
    },
    'COL': {
     'dk_abbreviation': 'COL',
     'dk_team_id': 4970,
     'nhl_id': 21,
     'name': 'Colorado Avalanche',
     'secondary_name': 'Colorado Avalanche',
     'slug': 'colorado-avalanche',
     'nhl_abbreviation': 'COL',
     'nst_abbreviation': 'COL',
    },
    'EDM': {
     'dk_abbreviation': 'EDM',
     'dk_team_id': 4959,
     'nhl_id': 22,
     'name': 'Edmonton Oilers',
     'secondary_name': 'Edmonton Oilers',
     'slug': 'edmonton-oilers',
     'nhl_abbreviation': 'EDM',
     'nst_abbreviation': 'EDM',
    },
    'VAN': {
     'dk_abbreviation': 'VAN',
     'dk_team_id': 4975,
     'nhl_id': 23,
     'name': 'Vancouver Canucks',
     'secondary_name': 'Vancouver Canucks',
     'slug': 'vancouver-canucks',
     'nhl_abbreviation': 'VAN',
     'nst_abbreviation': 'VAN',
    },
    'ANA': {
     'dk_abbreviation': 'ANH',
     'dk_team_id': 4978,
     'nhl_id': 24,
     'name': 'Anaheim Ducks',
     'secondary_name': 'Anaheim Ducks',
     'slug': 'anaheim-ducks',
     'nhl_abbreviation': 'ANA',
     'nst_abbreviation': 'ANA',
    },
    'DAL': {
     'dk_abbreviation': 'DAL',
     'dk_team_id': 4962,
     'nhl_id': 25,
     'name': 'Dallas Stars',
     'secondary_name': 'Dallas Stars',
     'slug': 'dallas-stars',
     'nhl_abbreviation': 'DAL',
     'nst_abbreviation': 'DAL',
    },
    'LAK': {
     'dk_abbreviation': 'LA',
     'dk_team_id': 4961,
     'nhl_id': 26,
     'name': 'Los Angeles Kings',
     'secondary_name': 'Los Angeles Kings',
     'slug': 'los-angeles-kings',
     'nhl_abbreviation': 'LAK',
     'nst_abbreviation': 'L.A',
    },
    'SJS': {
     'dk_abbreviation': 'SJ',
     'dk_team_id': 4971,
     'nhl_id': 28,
     'name': 'San Jose Sharks',
     'secondary_name': 'San Jose Sharks',
     'slug': 'san-jose-sharks',
     'nhl_abbreviation': 'SJS',
     'nst_abbreviation': 'S.J',
    },
    'CBJ': {
     'dk_abbreviation': 'CLS',
     'dk_team_id': 4982,
     'nhl_id': 29,
     'name': 'Columbus Blue Jackets',
     'secondary_name': 'Columbus Blue Jackets',
     'slug': 'columbus-blue-jackets',
     'nhl_abbreviation': 'CBJ',
     'nst_abbreviation': 'CBJ',
    },
    'MIN': {
     'dk_abbreviation': 'MIN',
     'dk_team_id': 4983,
     'nhl_id': 30,
     'name': 'Minnesota Wild',
     'secondary_name': 'Minnesota Wild',
     'slug': 'minnesota-wild',
     'nhl_abbreviation': 'MIN',
     'nst_abbreviation': 'MIN',
    },
    'WPG': {
     'dk_abbreviation': 'WPG',
     'dk_team_id': 4981,
     'nhl_id': 52,
     'name': 'Winnipeg Jets',
     'secondary_name': 'Winnipeg Jets',
     'slug': 'winnipeg-jets',
     'nhl_abbreviation': 'WPG',
     'nst_abbreviation': 'WPG',
    },
    'ARI': {
     'dk_abbreviation': 'ARI',
     'dk_team_id': 4977,
     'nhl_id': 53,
     'name': 'Arizona Coyotes',
     'secondary_name': 'Arizona Coyotes',
     'slug': 'arizona-coyotes',
     'nhl_abbreviation': 'ARI',
     'nst_abbreviation': 'ARI',
    },
    'VGK': {
     'dk_abbreviation': 'VGK',
     'dk_team_id': 58462,
     'nhl_id': 54,
     'name': 'Vegas Golden Knights',
     'secondary_name': 'Vegas Golden Knights',
     'slug': 'vegas-golden-knights',
     'nhl_abbreviation': 'VGK',
     'nst_abbreviation': 'VGK',
    }
}

# ------------------------------- #
# ------------------------------- #

# --------- #
#    NBA    #
# --------- #
NBA_TEAMS = {
  'ATL': {
    'tricode': 'ATL',
    'urlName': 'hawks',
    'teamShortName': 'Atlanta',
    'abbreviation': 'ATL',
    'dk_abbreviation': 'ATL',
    'name': 'Atlanta Hawks',
    'secondary_name': 'Atlanta Hawks',
    'nba_id': '1610612737',
    'dk_team_id': 0
  },
  'BOS': {
    'tricode': 'BOS',
    'urlName': 'celtics',
    'teamShortName': 'Boston',
    'abbreviation': 'BOS',
    'dk_abbreviation': 'BOS',
    'name': 'Boston Celtics',
    'secondary_name': 'Boston Celtics',
    'nba_id': '1610612738',
    'dk_team_id': 0
  },
  'BKN': {
    'tricode': 'BKN',
    'urlName': 'nets',
    'teamShortName': 'Brooklyn',
    'abbreviation': 'BKN',
    'dk_abbreviation': 'BKN',
    'name': 'Brooklyn Nets',
    'secondary_name': 'Brooklyn Nets',
    'nba_id': '1610612751',
    'dk_team_id': 0
  },
  'CHA': {
    'tricode': 'CHA',
    'urlName': 'hornets',
    'teamShortName': 'Charlotte',
    'abbreviation': 'CHA',
    'dk_abbreviation': 'CHA',
    'name': 'Charlotte Hornets',
    'secondary_name': 'Charlotte Hornets',
    'nba_id': '1610612766',
    'dk_team_id': 0
  },
  'CHI': {
    'tricode': 'CHI',
    'urlName': 'bulls',
    'teamShortName': 'Chicago',
    'abbreviation': 'CHI',
    'dk_abbreviation': 'CHI',
    'name': 'Chicago Bulls',
    'secondary_name': 'Chicago Bulls',
    'nba_id': '1610612741',
    'dk_team_id': 0
  },
  'CLE': {
    'tricode': 'CLE',
    'urlName': 'cavaliers',
    'teamShortName': 'Cleveland',
    'abbreviation': 'CLE',
    'dk_abbreviation': 'CLE',
    'name': 'Cleveland Cavaliers',
    'secondary_name': 'Cleveland Cavaliers',
    'nba_id': '1610612739',
    'dk_team_id': 0
  },
  'DAL': {
    'tricode': 'DAL',
    'urlName': 'mavericks',
    'teamShortName': 'Dallas',
    'abbreviation': 'DAL',
    'dk_abbreviation': 'DAL',
    'name': 'Dallas Mavericks',
    'secondary_name': 'Dallas Mavericks',
    'nba_id': '1610612742',
    'dk_team_id': 0
  },
  'DEN': {
    'tricode': 'DEN',
    'urlName': 'nuggets',
    'teamShortName': 'Denver',
    'abbreviation': 'DEN',
    'dk_abbreviation': 'DEN',
    'name': 'Denver Nuggets',
    'secondary_name': 'Denver Nuggets',
    'nba_id': '1610612743',
    'dk_team_id': 0
  },
  'DET': {
    'tricode': 'DET',
    'urlName': 'pistons',
    'teamShortName': 'Detroit',
    'abbreviation': 'DET',
    'dk_abbreviation': 'DET',
    'name': 'Detroit Pistons',
    'secondary_name': 'Detroit Pistons',
    'nba_id': '1610612765',
    'dk_team_id': 0
  },
  'GSW': {
    'tricode': 'GSW',
    'urlName': 'warriors',
    'teamShortName': 'Golden State',
    'abbreviation': 'GSW',
    'dk_abbreviation': 'GS',
    'name': 'Golden State Warriors',
    'secondary_name': 'Golden State Warriors',
    'nba_id': '1610612744',
    'dk_team_id': 0
  },
  'HOU': {
    'tricode': 'HOU',
    'urlName': 'rockets',
    'teamShortName': 'Houston',
    'abbreviation': 'HOU',
    'dk_abbreviation': 'HOU',
    'name': 'Houston Rockets',
    'secondary_name': 'Houston Rockets',
    'nba_id': '1610612745',
    'dk_team_id': 0
  },
  'IND': {
    'tricode': 'IND',
    'urlName': 'pacers',
    'teamShortName': 'Indiana',
    'abbreviation': 'IND',
    'dk_abbreviation': 'IND',
    'name': 'Indiana Pacers',
    'secondary_name': 'Indiana Pacers',
    'nba_id': '1610612754',
    'dk_team_id': 0
  },
  'LAC': {
    'tricode': 'LAC',
    'urlName': 'clippers',
    'teamShortName': 'LA Clippers',
    'abbreviation': 'LAC',
    'dk_abbreviation': 'LAC',
    'name': 'LA Clippers',
    'secondary_name': 'Los Angeles Clippers',
    'nba_id': '1610612746',
    'dk_team_id': 0
  },
  'LAL': {
    'tricode': 'LAL',
    'urlName': 'lakers',
    'teamShortName': 'L.A. Lakers',
    'abbreviation': 'LAL',
    'dk_abbreviation': 'LAL',
    'name': 'Los Angeles Lakers',
    'secondary_name': 'Los Angeles Lakers',
    'nba_id': '1610612747',
    'dk_team_id': 0
  },
  'MEM': {
    'tricode': 'MEM',
    'urlName': 'grizzlies',
    'teamShortName': 'Memphis',
    'abbreviation': 'MEM',
    'dk_abbreviation': 'MEM',
    'name': 'Memphis Grizzlies',
    'secondary_name': 'Memphis Grizzlies',
    'nba_id': '1610612763',
    'dk_team_id': 0
  },
  'MIA': {
    'tricode': 'MIA',
    'urlName': 'heat',
    'teamShortName': 'Miami',
    'abbreviation': 'MIA',
    'dk_abbreviation': 'MIA',
    'name': 'Miami Heat',
    'secondary_name': 'Miami Heat',
    'nba_id': '1610612748',
    'dk_team_id': 0
  },
  'MIL': {
    'tricode': 'MIL',
    'urlName': 'bucks',
    'teamShortName': 'Milwaukee',
    'abbreviation': 'MIL',
    'dk_abbreviation': 'MIL',
    'name': 'Milwaukee Bucks',
    'secondary_name': 'Milwaukee Bucks',
    'nba_id': '1610612749',
    'dk_team_id': 0
  },
  'MIN': {
    'tricode': 'MIN',
    'urlName': 'timberwolves',
    'teamShortName': 'Minnesota',
    'abbreviation': 'MIN',
    'dk_abbreviation': 'MIN',
    'name': 'Minnesota Timberwolves',
    'secondary_name': 'Minnesota Timberwolves',
    'nba_id': '1610612750',
    'dk_team_id': 0
  },
  'NOP': {
    'tricode': 'NOP',
    'urlName': 'pelicans',
    'teamShortName': 'New Orleans',
    'abbreviation': 'NOP',
    'dk_abbreviation': 'NOP',
    'name': 'New Orleans Pelicans',
    'secondary_name': 'New Orleans Pelicans',
    'nba_id': '1610612740',
    'dk_team_id': 0
  },
  'NYK': {
    'tricode': 'NYK',
    'urlName': 'knicks',
    'teamShortName': 'New York',
    'abbreviation': 'NYK',
    'dk_abbreviation': 'NYK',
    'name': 'New York Knicks',
    'secondary_name': 'New York Knicks',
    'nba_id': '1610612752',
    'dk_team_id': 0
  },
  'OKC': {
    'tricode': 'OKC',
    'urlName': 'thunder',
    'teamShortName': 'Oklahoma City',
    'abbreviation': 'OKC',
    'dk_abbreviation': 'OKC',
    'name': 'Oklahoma City Thunder',
    'secondary_name': 'Oklahoma City Thunder',
    'nba_id': '1610612760',
    'dk_team_id': 0
  },
  'ORL': {
    'tricode': 'ORL',
    'urlName': 'magic',
    'teamShortName': 'Orlando',
    'abbreviation': 'ORL',
    'dk_abbreviation': 'ORL',
    'name': 'Orlando Magic',
    'secondary_name': 'Orlando Magic',
    'nba_id': '1610612753',
    'dk_team_id': 0
  },
  'PHI': {
    'tricode': 'PHI',
    'urlName': 'sixers',
    'teamShortName': 'Philadelphia',
    'abbreviation': 'PHI',
    'dk_abbreviation': 'PHI',
    'name': 'Philadelphia 76ers',
    'secondary_name': 'Philadelphia 76ers',
    'nba_id': '1610612755',
    'dk_team_id': 0
  },
  'PHX': {
    'tricode': 'PHX',
    'urlName': 'suns',
    'teamShortName': 'Phoenix',
    'abbreviation': 'PHX',
    'dk_abbreviation': 'PHO',
    'name': 'Phoenix Suns',
    'secondary_name': 'Phoenix Suns',
    'nba_id': '1610612756',
    'dk_team_id': 0
  },
  'POR': {
    'tricode': 'POR',
    'urlName': 'blazers',
    'teamShortName': 'Portland',
    'abbreviation': 'POR',
    'dk_abbreviation': 'POR',
    'name': 'Portland Trail Blazers',
    'secondary_name': 'Portland Trail Blazers',
    'nba_id': '1610612757',
    'dk_team_id': 0
  },
  'SAC': {
    'tricode': 'SAC',
    'urlName': 'kings',
    'teamShortName': 'Sacramento',
    'abbreviation': 'SAC',
    'dk_abbreviation': 'SAC',
    'name': 'Sacramento Kings',
    'secondary_name': 'Sacramento Kings',
    'nba_id': '1610612758',
    'dk_team_id': 0
  },
  'SAS': {
    'tricode': 'SAS',
    'urlName': 'spurs',
    'teamShortName': 'San Antonio',
    'abbreviation': 'SAS',
    'dk_abbreviation': 'SA',
    'name': 'San Antonio Spurs',
    'secondary_name': 'San Antonio Spurs',
    'nba_id': '1610612759',
    'dk_team_id': 0
  },
  'TOR': {
    'tricode': 'TOR',
    'urlName': 'raptors',
    'teamShortName': 'Toronto',
    'abbreviation': 'TOR',
    'dk_abbreviation': 'TOR',
    'name': 'Toronto Raptors',
    'secondary_name': 'Toronto Raptors',
    'nba_id': '1610612761',
    'dk_team_id': 0
  },
  'UTA': {
    'tricode': 'UTA',
    'urlName': 'jazz',
    'teamShortName': 'Utah',
    'abbreviation': 'UTA',
    'dk_abbreviation': 'UTA',
    'name': 'Utah Jazz',
    'secondary_name': 'Utah Jazz',
    'nba_id': '1610612762',
    'dk_team_id': 0
  },
  'WAS': {
    'tricode': 'WAS',
    'urlName': 'wizards',
    'teamShortName': 'Washington',
    'abbreviation': 'WAS',
    'dk_abbreviation': 'WAS',
    'name': 'Washington Wizards',
    'secondary_name': 'Washington Wizards',
    'nba_id': '1610612764',
    'dk_team_id': 0
  }
}

# --------- #
#    MLB    #
# --------- #
MLB_TEAMS = {

}

# --------- #
#    NFL    #
# --------- #
NFL_TEAMS = {
  'TEN': {
        'nfl': 'TEN',
        'dk': 'TEN',
        'dk_id': 336,
        'nfl_team_id': 2100,
        'pfr': 'OTI',
        'pff': 31,
        'pfflabel': 'TEN',
        'fo': 'TEN',
        'full': 'Tennessee Titans',
        'location': 'Tennessee',
        'short_location': 'Tennessee',
        'nickname': 'Titans',
        'slug': 'tennessee-titans'
  },
  'IND': {
          'nfl': 'IND',
          'dk': 'IND',
          'dk_id': 338,
          'nfl_team_id': 2200,
          'pfr': 'CLT',
          'pff': 14,
          'pfflabel': 'IND',
          'fo': 'IND',
          'full': 'Indianapolis Colts',
          'location': 'Indianapolis',
          'short_location': 'Indianapolis',
          'nickname': 'Colts',
          'slug': 'indianapolis-colts'
  },
  'ARI': {
          'nfl': 'ARI',
          'dk': 'ARI',
          'dk_id': 355,
          'nfl_team_id': 3800,
          'pfr': 'CRD',
          'pff': 1,
          'pfflabel': 'ARZ',
          'fo': 'ARI',
          'full': 'Arizona Cardinals',
          'location': 'Arizona',
          'short_location': 'Arizona',
          'nickname': 'Cardinals',
          'slug': 'arizona-cardinals'
  },
  'NE': {
          'nfl': 'NE',
          'dk': 'NE',
          'dk_id': 348,
          'nfl_team_id': 3200,
          'pfr': 'NWE',
          'pff': 19,
          'pfflabel': 'NE',
          'fo': 'NE',
          'full': 'New England Patriots',
          'location': 'New England',
          'short_location': 'New England',
          'nickname': 'Patriots',
          'slug': 'new-england-patriots'
  },
  'CLE': {
          'nfl': 'CLE',
          'dk': 'CLE',
          'dk_id': 329,
          'nfl_team_id': 1050,
          'pfr': 'CLE',
          'pff': 8,
          'pfflabel': 'CLV',
          'fo': 'CLE',
          'full': 'Cleveland Browns',
          'location': 'Cleveland',
          'short_location': 'Cleveland',
          'nickname': 'Browns',
          'slug': 'cleveland-browns'
  },
  'JAX': {
          'nfl': 'JAC',
          'dk': 'JAX',
          'dk_id': 365,
          'nfl_team_id': 2250,
          'pfr': 'JAX',
          'pff': 15,
          'pfflabel': 'JAX',
          'fo': 'JAX',
          'full': 'Jacksonville Jaguars',
          'location': 'Jacksonville',
          'short_location': 'Jacksonville',
          'nickname': 'Jaguars',
          'slug': 'jacksonville-jaguars'
  },
  'MIA': {
          'nfl': 'MIA',
          'dk': 'MIA',
          'dk_id': 345,
          'nfl_team_id': 2700,
          'pfr': 'MIA',
          'pff': 17,
          'pfflabel': 'MIA',
          'fo': 'MIA',
          'full': 'Miami Dolphins',
          'location': 'Miami',
          'short_location': 'Miami',
          'nickname': 'Dolphins',
          'slug': 'miami-dolphins'
  },
  'NYJ': {
          'nfl': 'NYJ',
          'dk': 'NYJ',
          'dk_id': 352,
          'nfl_team_id': 3430,
          'pfr': 'NYJ',
          'pff': 22,
          'pfflabel': 'NYJ',
          'fo': 'NYJ',
          'full': 'New York Jets',
          'location': 'New York Jets',
          'short_location': 'NY Jets',
          'nickname': 'Jets',
          'slug': 'new-york-jets'
  },
  'LV': {
          'nfl': 'LV',
          'dk': 'LV',
          'dk_id': 341,
          'nfl_team_id': 2520,
          'pfr': 'VEG',
          'pff': 23,
          'pfflabel': 'LV',
          'fo': 'LV',
          'full': 'Las Vegas Raiders',
          'location': 'Las Vegas',
          'short_location': 'Las Vegas',
          'nickname': 'Raiders',
          'slug': 'las-vegas-raiders'
  },
  'ATL': {
          'nfl': 'ATL',
          'dk': 'ATL',
          'dk_id': 323,
          'nfl_team_id': 200,
          'pfr': 'ATL',
          'pff': 2,
          'pfflabel': 'ATL',
          'fo': 'ATL',
          'full': 'Atlanta Falcons',
          'location': 'Atlanta',
          'short_location': 'Atlanta',
          'nickname': 'Falcons',
          'slug': 'atlanta-falcons'
  },
  'NYG': {
          'nfl': 'NYG',
          'dk': 'NYG',
          'dk_id': 351,
          'nfl_team_id': 3410,
          'pfr': 'NYG',
          'pff': 21,
          'pfflabel': 'NYG',
          'fo': 'NYG',
          'full': 'New York Giants',
          'location': 'New York Giants',
          'short_location': 'NY Giants',
          'nickname': 'Giants',
          'slug': 'new-york-giants'
  },
  'CIN': {
          'nfl': 'CIN',
          'dk': 'CIN',
          'dk_id': 327,
          'nfl_team_id': 920,
          'pfr': 'CIN',
          'pff': 7,
          'pfflabel': 'CIN',
          'fo': 'CIN',
          'full': 'Cincinnati Bengals',
          'location': 'Cincinnati',
          'short_location': 'Cincinnati',
          'nickname': 'Bengals',
          'slug': 'cincinnati-bengals'
  },
  'LAC': {
          'nfl': 'LAC',
          'dk': 'LAC',
          'dk_id': 357,
          'nfl_team_id': 4400,
          'pfr': 'SDG',
          'pff': 27,
          'pfflabel': 'LAC',
          'fo': 'LAC',
          'full': 'Los Angeles Chargers',
          'location': 'Los Angeles Chargers',
          'short_location': 'LA Chargers',
          'nickname': 'Chargers',
          'slug': 'los-angeles-chargers'
  },
  'BUF': {
          'nfl': 'BUF',
          'dk': 'BUF',
          'dk_id': 324,
          'nfl_team_id': 610,
          'pfr': 'BUF',
          'pff': 4,
          'pfflabel': 'BUF',
          'fo': 'BUF',
          'full': 'Buffalo Bills',
          'location': 'Buffalo',
          'short_location': 'Buffalo',
          'nickname': 'Bills',
          'slug': 'buffalo-bills'
  },
  'CAR': {
          'nfl': 'CAR',
          'dk': 'CAR',
          'dk_id': 364,
          'nfl_team_id': 750,
          'pfr': 'CAR',
          'pff': 5,
          'pfflabel': 'CAR',
          'fo': 'CAR',
          'full': 'Carolina Panthers',
          'location': 'Carolina',
          'short_location': 'Carolina',
          'nickname': 'Panthers',
          'slug': 'carolina-panthers'
  },
  'MIN': {
          'nfl': 'MIN',
          'dk': 'MIN',
          'dk_id': 347,
          'nfl_team_id': 3000,
          'pfr': 'MIN',
          'pff': 18,
          'pfflabel': 'MIN',
          'fo': 'MIN',
          'full': 'Minnesota Vikings',
          'location': 'Minnesota',
          'short_location': 'Minnesota',
          'nickname': 'Vikings',
          'slug': 'minnesota-vikings'
  },
  'NO': {
          'nfl': 'NO',
          'dk': 'NO',
          'dk_id': 350,
          'nfl_team_id': 3300,
          'pfr': 'NOR',
          'pff': 20,
          'pfflabel': 'NO',
          'fo': 'NO',
          'full': 'New Orleans Saints',
          'location': 'New Orleans',
          'short_location': 'New Orleans',
          'nickname': 'Saints',
          'slug': 'new-orleans-saints'
  },
  'DEN': {
          'nfl': 'DEN',
          'dk': 'DEN',
          'dk_id': 332,
          'nfl_team_id': 1400,
          'pfr': 'DEN',
          'pff': 10,
          'pfflabel': 'DEN',
          'fo': 'DEN',
          'full': 'Denver Broncos',
          'location': 'Denver',
          'short_location': 'Denver',
          'nickname': 'Broncos',
          'slug': 'denver-broncos'
  },
  'SF': {
          'nfl': 'SF',
          'dk': 'SF',
          'dk_id': 359,
          'nfl_team_id': 4500,
          'pfr': 'SFO',
          'pff': 28,
          'pfflabel': 'SF',
          'fo': 'SF',
          'full': 'San Francisco 49ers',
          'location': 'San Francisco',
          'short_location': 'San Francisco',
          'nickname': '49ers',
          'slug': 'san-francisco-49ers'
  },
  'KC': {
          'nfl': 'KC',
          'dk': 'KC',
          'dk_id': 339,
          'nfl_team_id': 2310,
          'pfr': 'KAN',
          'pff': 16,
          'pfflabel': 'KC',
          'fo': 'KC',
          'full': 'Kansas City Chiefs',
          'location': 'Kansas City',
          'short_location': 'Kansas City',
          'nickname': 'Chiefs',
          'slug': 'kansas-city-chiefs'
  },
  'TB': {
          'nfl': 'TB',
          'dk': 'TB',
          'dk_id': 362,
          'nfl_team_id': 4900,
          'pfr': 'TAM',
          'pff': 30,
          'pfflabel': 'TB',
          'fo': 'TB',
          'full': 'Tampa Bay Buccaneers',
          'location': 'Tampa Bay',
          'short_location': 'Tampa Bay',
          'nickname': 'Buccaneers',
          'slug': 'tampa-bay-buccaneers'
  },
  'BAL': {
          'nfl': 'BAL',
          'dk': 'BAL',
          'dk_id': 366,
          'nfl_team_id': 325,
          'pfr': 'RAV',
          'pff': 3,
          'pfflabel': 'BLT',
          'fo': 'BAL',
          'full': 'Baltimore Ravens',
          'location': 'Baltimore',
          'short_location': 'Baltimore',
          'nickname': 'Ravens',
          'slug': 'baltimore-ravens'
  },
  'PIT': {
          'nfl': 'PIT',
          'dk': 'PIT',
          'dk_id': 356,
          'nfl_team_id': 3900,
          'pfr': 'PIT',
          'pff': 25,
          'pfflabel': 'PIT',
          'fo': 'PIT',
          'full': 'Pittsburgh Steelers',
          'location': 'Pittsburgh',
          'short_location': 'Pittsburgh',
          'nickname': 'Steelers',
          'slug': 'pittsburgh-steelers'
  },
  'WAS': {
          'nfl': 'WAS',
          'dk': 'WAS',
          'dk_id': 363,
          'nfl_team_id': 5110,
          'pfr': 'WAS',
          'pff': 32,
          'pfflabel': 'WAS',
          'fo': 'WAS',
          'full': 'Washington Football Team',
          'location': 'Washington',
          'short_location': 'Washington',
          'nickname': 'Football Team',
          'slug': 'washington-football-team'
  },
  'CHI': {
          'nfl': 'CHI',
          'dk': 'CHI',
          'dk_id': 326,
          'nfl_team_id': 810,
          'pfr': 'CHI',
          'pff': 6,
          'pfflabel': 'CHI',
          'fo': 'CHI',
          'full': 'Chicago Bears',
          'location': 'Chicago',
          'short_location': 'Chicago',
          'nickname': 'Bears',
          'slug': 'chicago-bears'
  },
  'GB': {
          'nfl': 'GB',
          'dk': 'GB',
          'dk_id': 335,
          'nfl_team_id': 1800,
          'pfr': 'GNB',
          'pff': 12,
          'pfflabel': 'GB',
          'fo': 'GB',
          'full': 'Green Bay Packers',
          'location': 'Green Bay',
          'short_location': 'Green Bay',
          'nickname': 'Packers',
          'slug': 'green-bay-packers'
  },
  'PHI': {
          'nfl': 'PHI',
          'dk': 'PHI',
          'dk_id': 354,
          'nfl_team_id': 3700,
          'pfr': 'PHI',
          'pff': 24,
          'pfflabel': 'PHI',
          'fo': 'PHI',
          'full': 'Philadelphia Eagles',
          'location': 'Philadelphia',
          'short_location': 'Philadelphia',
          'nickname': 'Eagles',
          'slug': 'philadelphia-eagles'
  },
  'HOU': {
          'nfl': 'HOU',
          'dk': 'HOU',
          'dk_id': 325,
          'nfl_team_id': 2120,
          'pfr': 'HTX',
          'pff': 13,
          'pfflabel': 'HST',
          'fo': 'HOU',
          'full': 'Houston Texans',
          'location': 'Houston',
          'short_location': 'Houston',
          'nickname': 'Texans',
          'slug': 'houston-texans'
  },
  'DET': {
          'nfl': 'DET',
          'dk': 'DET',
          'dk_id': 334,
          'nfl_team_id': 1540,
          'pfr': 'DET',
          'pff': 11,
          'pfflabel': 'DET',
          'fo': 'DET',
          'full': 'Detroit Lions',
          'location': 'Detroit',
          'short_location': 'Detroit',
          'nickname': 'Lions',
          'slug': 'detroit-lions'
  },
  'DAL': {
          'nfl': 'DAL',
          'dk': 'DAL',
          'dk_id': 331,
          'nfl_team_id': 1200,
          'pfr': 'DAL',
          'pff': 9,
          'pfflabel': 'DAL',
          'fo': 'DAL',
          'full': 'Dallas Cowboys',
          'location': 'Dallas',
          'short_location': 'Dallas',
          'nickname': 'Cowboys',
          'slug': 'dallas-cowboys'
  },
  'SEA': {
          'nfl': 'SEA',
          'dk': 'SEA',
          'dk_id': 361,
          'nfl_team_id': 4600,
          'pfr': 'SEA',
          'pff': 29,
          'pfflabel': 'SEA',
          'fo': 'SEA',
          'full': 'Seattle Seahawks',
          'location': 'Seattle',
          'short_location': 'Seattle',
          'nickname': 'Seahawks',
          'slug': 'seattle-seahawks'
  },
  'LAR': {
          'nfl': 'LA',
          'dk': 'LAR',
          'dk_id': 343,
          'nfl_team_id': 2510,
          'pfr': 'RAM',
          'pff': 26,
          'pfflabel': 'LA',
          'fo': 'LAR',
          'full': 'Los Angeles Rams',
          'location': 'Los Angeles Rams',
          'short_location': 'LA Rams',
          'nickname': 'Rams',
          'slug': 'los-angeles-rams'
  }
}

# ----------------------------------------- #
#          Sports IDs, keys, names          #
#  Keys are named by 'title' from odds API  #
# ----------------------------------------- #
SPORTS = {
    'NFL':{
        'dk_id': 1,
        'dk_name': 'NFL',
        'odds_key': 'americanfootball_nfl',
        'sport_data': NFL_TEAMS
    },
    'NHL':{
        'dk_id': 3,
        'dk_name': 'NHL',
        'odds_key': 'icehockey_nhl',
        'sport_data': NHL_TEAMS
    },
    'NBA':{
        'dk_id': 4,
        'dk_name': 'NBA',
        'odds_key': 'basketball_nba',
        'sport_data': NBA_TEAMS
    },
    'MLB':{
        'dk_id': 2,
        'dk_name': 'MLB',
        'odds_key': 'baseball_mlb',
        'sport_data': MLB_TEAMS
    },
    'GOLF':{
        'dk_id': 13,
        'dk_name': 'GOLF'
    },
    'EPL':{
        'dk_id': 12,
        'dk_name': 'SOC',
        'odds_key': 'soccer_epl'
    },
    'NCAAF':{
        'dk_id': 5,
        'dk_name': 'CFB',
        'odds_key': 'americanfootball_ncaaf'
    },
    'NCAAB':{
        'dk_id': 6,
        'dk_name': 'CBB',
        'odds_key': 'basketball_ncaab'
    },
    'MMA':{
        'dk_id': 9,
        'dk_name': 'MMA',
        'odds_key': 'mma_mixed_martial_arts'
    }
}

#


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Tue Aug  4 17:12:07 2020

@author: jyotivashishth
"""

from plotnine import *
import pandas as pd

#input path
_input_path = os.path.abspath(os.path.dirname('__file__'))

print(" Train ")
#read entire Data set
pdPdFrm_Train_Tweets1 = pd.read_csv(os.path.join(_input_path , "Train_Data.csv"),encoding = "ISO-8859-1")
pdPdFrm_Train_Tweets1['SentimentText_old']  = pdPdFrm_Train_Tweets1['SentimentText'].copy()
pdPdFrm_Train_Tweets1['SentimentText'] = transform(pdPdFrm_Train_Tweets1['SentimentText'], "Y")
pdPdFrm_Train_Tweets = pdPdFrm_Train_Tweets1.copy()

#Train Analysis
#read entire Data set
pdPdFrm_Train_Tweets1 = pd.read_csv(os.path.join(_input_path , "Train_Data.csv"),encoding = "ISO-8859-1")
pdPdFrm_Train_Tweets1['SentimentText_old']  = pdPdFrm_Train_Tweets1['SentimentText'].copy()
pdPdFrm_Train_Tweets1['SentimentText'] = transform(pdPdFrm_Train_Tweets1['SentimentText'], "Y")
pdPdFrm_Train_Tweets = pdPdFrm_Train_Tweets1.copy()

#analysis
pdPdFrm_Train_Tweets['Length_of_Tweets'] = pdPdFrm_Train_Tweets['SentimentText'].str.len()

my_plot = ggplot(data=pdPdFrm_Train_Tweets, mapping=aes(x='Sentiment' , fill='Sentiment' )) +\
    geom_bar(stat="bin", width=1)+\
    labs(title = "Tweets Distribution Train Dataset" , x = "Sentiment")
print(my_plot)

print("No of Positive tweets :")
print(len(pdPdFrm_Train_Tweets.query("Sentiment=='Positive'")))
print("No of Negative tweets :")
print(len(pdPdFrm_Train_Tweets.query("Sentiment=='Negative'")))

#Test Analysis

#read entire Data set
pdPdFrm_Test_Tweets = pd.read_csv(os.path.join(_input_path , "Test_Data.csv"),encoding = "ISO-8859-1")

#accuracy : 71.27384012241347
pdPdFrm_Test_Tweets = pd.read_csv(os.path.join(_input_path , "Test_Data.csv"),encoding = "ISO-8859-1")

pdPdFrm_Test_Tweets['Length_of_Tweets'] = pdPdFrm_Test_Tweets['SentimentText'].str.len()

my_plot = ggplot(data=pdPdFrm_Test_Tweets, mapping=aes(x='Sentiment' , fill='Sentiment' )) +\
    geom_bar(stat="bin", width=1)+\
    labs(title = "Tweets Distribution Test Dataset" , x = "Sentiment")
print(my_plot)

print("No of Positive tweets :")
print(len(pdPdFrm_Test_Tweets.query("Sentiment=='Positive'")))
print("No of Negative tweets :")
print(len(pdPdFrm_Test_Tweets.query("Sentiment=='Negative'")))

print("Understanding the data")
frames =  [pdPdFrm_Train_Tweets, pdPdFrm_Test_Tweets]
#complete data frame
Complete_DataFrame = pd.concat(frames)

average_len = sum(Complete_DataFrame["Length_of_Tweets"])/len(Complete_DataFrame["Length_of_Tweets"])

print("Average length of tweets :")
print(average_len)

my_plot = ggplot(data=Complete_DataFrame, mapping=aes(x='Length_of_Tweets')) +\
    geom_histogram(color='white', bins=100 , fill="steelblue")+\
    labs(title = "Length of all Tweets" , x = "Length of Tweets")
print(my_plot)

print("Top 25 used words ....................")

for key in all_words:
   words.append(key)
   freqs.append(all_words[key])

X_val = words[:50]
Y_val = freqs[:50]

pd_frame_val = pd.DataFrame({"Words":X_val, "Frequency":Y_val},
                            columns=["Words", "Frequency"])

my_plot = ggplot(data=pd_frame_val, mapping=aes(x='Words' )) +\
    geom_col (aes(y="Frequency" ) , fill="steelblue")+\
    labs(title = "Word Frequency Distribution(Allowed distribution - adjective,adverb,Noun)",\
         x = "Words")+\
    theme(axis_text_x=element_text(rotation=90, hjust=1))
print(my_plot)

#accuracy Plot
Classifier_names = ['MultinomialNB','Logistic Regression','SGD',
'AdaBoost (Linear SVC)','AdaBoostClassifier (LogisticRegression)',
'Voted Classifier(Train)' , 'Voted Classifier(Test)']

Accuracy_percentage = [ 73.298,
73.291,
73.257,
71.68,
72.017,
73.918,
71.899]

pd_frame_val = pd.DataFrame({"Classifier":Classifier_names,\
"AccuracyPercentage":Accuracy_percentage},\
                    columns=["Classifier", "AccuracyPercentage"])

my_plot = ggplot(data=pd_frame_val, mapping=aes(x='Classifier' )) +\
    geom_col (aes(y="AccuracyPercentage" ) , fill="steelblue")+\
    labs(title = "Classifier accuracies (Allowed words - adjective,adverb,Noun)",\
         x = "Classifiers")+\
    geom_text(aes(y="AccuracyPercentage", label="AccuracyPercentage"))+\
    theme(axis_text_x=element_text(rotation=90, hjust=1))
print(my_plot)

print("Top 25 used words ....................")
words=[]
freqs=[]
for key in all_words:
   words.append(key)
   freqs.append(all_words[key])

X_val = words[:50]
Y_val = freqs[:50]

pd_frame_val = pd.DataFrame({"Words":X_val, "Frequency":Y_val},
                            columns=["Words", "Frequency"])

my_plot = ggplot(data=pd_frame_val, mapping=aes(x='Words' )) +\
    geom_col (aes(y="Frequency" ) , fill="steelblue")+\
    labs(title = "Word Frequency Distribution(Allowed distribution - adjective,adverb)",\
         x = "Words")+\
    theme(axis_text_x=element_text(rotation=90, hjust=1))
print(my_plot)

#accuracy Plot
Classifier_names = ['1-MultinomialNB','2-Logistic Regression','3-SGD',
'4-AdaBoost (Linear SVC)','5-AdaBoostClassifier (LogisticRegression)',
'6-Voted Classifier(Train)' , '7-Voted Classifier(Test)']

Accuracy_percentage = [ 73.11,73.44, 73.34 ,71.47,70.91,73.56,71.89]

pd_frame_val = pd.DataFrame({"Classifier":Classifier_names,\
"AccuracyPercentage":Accuracy_percentage},\
                    columns=["Classifier", "AccuracyPercentage"])


import pandas as pd
import numpy as np
import os
import random

final_csv_path = '../data/halfway/csv/'

def create_final_csv():

    df    = pd.read_csv('../data/halfway/csv/reduced_train.csv')
    stats = pd.read_csv('../data/halfway/csv/stats_128.csv')
    euler = pd.read_csv('../data/halfway/csv/euler_128.csv')

    df.sort_values('image_name', inplace=True, ignore_index = True)
    stats.sort_values('image_name', inplace=True, ignore_index = True)
    euler.sort_values('image_name', inplace=True, ignore_index = True)

    df.drop(['patient_id', 'diagnosis'], axis=1)

    final = df.merge(stats, on = 'image_name', validate = 'one_to_one')
    final = final.merge(euler, on = 'image_name', validate = 'one_to_one')

    columns_titles = [
        'image_name', 'sex', 'anatom_site_general_challenge', 'age_approx',
        'areas_ratio', 'major_axis_weighted_mean', 'minor_axis_weighted_mean', 'eccentricity_mean', 'eccentricity_std',
        'R_mean', 'G_mean', 'B_mean', 'R_std', 'G_std', 'B_std',
        'euler0', 'euler1', 'euler2', 'euler3', 'euler4',
        'benign_malignant', 'target'
    ]

    final=final.reindex(columns=columns_titles)

    final.fillna(0, inplace=True)

    print(final.head())

    final.to_csv(final_csv_path + 'submodels.csv', index = False)

    csv_checkup()

def csv_checkup():
    df    = pd.read_csv('../data/halfway/csv/reduced_train.csv')
    stats = pd.read_csv('../data/halfway/csv/stats_128.csv')
    euler = pd.read_csv('../data/halfway/csv/euler_128.csv')
    final = pd.read_csv(final_csv_path + 'submodels.csv')

    df.fillna(0, inplace=True)
    stats.fillna(0, inplace=True)
    euler.fillna(0, inplace=True)
    final.fillna(0, inplace=True)

    df.sort_values('image_name'   , inplace=True, ignore_index = True)
    stats.sort_values('image_name', inplace=True, ignore_index = True)
    euler.sort_values('image_name', inplace=True, ignore_index = True)
    final.sort_values('image_name', inplace=True, ignore_index = True)

    df.drop(['patient_id', 'diagnosis'], axis=1)
    columns_titles = [
        'image_name', 'sex', 'anatom_site_general_challenge', 'age_approx', 'benign_malignant', 'target'
    ]
    df=df.reindex(columns=columns_titles)

    for i in df.image_name.values:
        if not all(final[final['image_name']==i].iloc[:, :4] == df[df['image_name']==i].iloc[:, :4]):
            raise Exception('ERROR in image_name: {}'.format(i))
        if not all(final[final['image_name']==i].iloc[:, 20:] == df[df['image_name']==i].iloc[:, 4:]):
            raise Exception('ERROR in image_name: {}'.format(i))
        if not all(final[final['image_name']==i].iloc[:, 4:15] == stats[stats['image_name']==i].iloc[:, 1:]):
            raise Exception('ERROR in image_name: {}'.format(i))
        if not all(final[final['image_name']==i].iloc[:, 15:20] == euler[euler['image_name']==i].iloc[:, 1:]):
            raise Exception('ERROR in image_name: {}'.format(i))

    print('EVERITHING IS OK')

if __name__ == "__main__":
    create_final_csv()


import argparse

from housePrediction import ingest_data, score, train

def model_result():

    data_path = "./datasets/housing/housing.csv"

    # Get data
    ingest_data_class = ingest_data.IngestData()
    (
        housing_prepared,
        test_data,
        housing_labels,
    ) = ingest_data_class.prepare_dataset()

    # Train model and fit
    model_detail = train.TrainModel(
        housing_prepared,
        housing_labels,
        model_output_folder="artifacts/",
    )
    model_linear = model_detail.linear_model()

    # Score model
    scores = score.CalculateScore(model="linear", metric="mae")
    results = scores.calculate_score()

    return results

if __name__ == "__main__":

    # Take hostname and port number from User
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--hostname",
        "-ip",
        help="IP to connect to",
        default="localhost",
    )
    parser.add_argument("--portname", "-p", help="port number", default="5000")
    parser.add_argument(
        "--experiment_name",
        "-e",
        default="House_prediction",
        help="Experiment name",
    )

    args = parser.parse_args()

    print(model_result())


import os
import sys
import re
if len(sys.argv) < 4:
    print("bookCutter.py - Cut a book into chapters and smaller configurable sections.\n\n Usage: python bookcutter.py [bookname] [delimiterText] [path/to/book] [pageWordCount]\n\nExample: bookcutter.py Dracula CHAPTER ./dracula.txt 425\n\nIt is recommended to remove all non chapter text from the file before running.")
    sys.exit()
sectionDelim = "SECTIONREPLACE61723"
bookName = sys.argv[1]
delimiterText = sys.argv[2]
bookFile = sys.argv[3]
pageWordCount = int(sys.argv[4])
curWordCount = 0
# cut up on delimiter
chapters = []
chapterCount = -1
# Create edit book file
editedBookFileName = bookFile + "-edited"
editedBookFile = open(editedBookFileName, "a+")
with open(bookFile, "r") as book:
    for line in book:
        words = line.lower()
        words = re.sub("[^\w ]", "", words)
        wordsLen = words.split(" ")
        if len(wordsLen) + curWordCount > pageWordCount:
            line = sectionDelim + line
            curWordCount = 0
        else:
            curWordCount += len(wordsLen)
        if chapterCount == -1:
            chapters.append(line)
            chapterCount += 1
            continue
        if delimiterText in line:
            chapterCount += 1
            chapters.append("\n")
        chapters[chapterCount] += line
# make folders and files for chapters. Write the chapter files out.
chapterCount = 1
for chapter in chapters:
    editedBookFile.write(chapter)
    sections = chapter.replace("\n","",1).split(sectionDelim)
    folderPath = bookName + "-Chapter" + str(chapterCount)
    chapterFolder = folderPath + "/Chapter" + str(chapterCount) + ".txt"
    os.mkdir(folderPath)
    chapterFile = open(chapterFolder,"w")
    chapterFile.write(chapter)
    sectionCount = 1
    sectionFile = open(folderPath + "/sections" + ".txt", "w")
    for section in sections:
        sectionFile.write(section.replace("\n"," "))
        sectionFile.write("\n")
        sectionCount += 1
        # Find section in editedbook and insert image insert callout
    chapterCount += 1
# cut into smaller pieces


"""
Write a HashTable class that stores strings
in a hash table, where keys are calculated
using the first two letters of the string.

>>> hash_table = HashTable()
>>> print ( calculate_hash_value('UDACITY'))
8568
>>> print (hash_table.lookup('UDACITY'))
-1
>>> hash_table.store('UDACITY')
>>> print (hash_table.lookup('UDACITY'))
8568
>>> hash_table.store('UDACIOUS')
>>> print (hash_table.lookup('UDACIOUS'))
8568
"""

def calculate_hash_value(string):
    """Helper function to calulate a
    hash value from a string."""
    return ord(string[0]) * 100 + ord(string[1])

class HashTable(object):
    def __init__(self):
        self.table = [None] * 10000

    def store(self, string):
        """Input a string that's stored in
        the table."""
        _hash = calculate_hash_value(string)
        if self.table[_hash]:
            self.table[_hash].append(string)
        else:
            self.table[_hash] = [string]

    def lookup(self, string):
        """Return the hash value if the
        string is already in the table.
        Return -1 otherwise."""
        _hash = calculate_hash_value(string)
        if self.table[_hash]:
            for item in self.table[_hash]:
                if item == string:
                    return _hash
        return -1

if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=True)


from setuptools import setup, find_packages

VERSION = 0.1

setup(
  name="ykxtools",
  version=VERSION,
  packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]),
  install_requires=[
    "ipaddress",
    "psutil",
  ],
  entry_points={

    "tools_scripts": [
      "split_str = mytools.mytools:split_str",
      "read_json_file = mytools.mytools.read_json_file",

    ],
  },

)


from pathlib import Path

import pandas as pd
import requests
import torch
from eyepacs import EyePACS

from EyeQ_process import process

class EyeQ(EyePACS):

    # Create EyeQ dataset class attributes
    image_extension = ".jpg"
    result_label = "quality"
    classes_names = ["Gradable", "Usable", "Ungradable"]

    def __init__(
        self,
        targ_dir: Path = "",  # also called root
        split: str = "train",
        transform=None,
        target_transform=None,
    ) -> None:
        super().__init__(targ_dir, split, transform, target_transform)

        self.__preprocess()

        self.label_csv_name = f"Label_EyeQ_{split}.csv"

        self.__download_results()

        self.df_labels = pd.read_csv(self.data_path / self.label_csv_name, sep=",")

        self.df_labels["image"] = self.df_labels["image"].apply(
            lambda S: S.strip(".jpeg")
        )

    def __download_results(
        self,
    ) -> None:
        # Download EyeQ results
        with open(self.data_path / self.label_csv_name, "wb") as f:
            request = requests.get(
                f"https://raw.githubusercontent.com/HzFu/EyeQ/master/data/{self.label_csv_name}"
            )
            print(f"Downloading Label_EyeQ_{self.split}.csv...")
            f.write(request.content)

    def __preprocess(
        self,
    ) -> None:
        image_list = [str(img) for img in self.paths]
        save_path = str(self.data_path / self.split)

        process(image_list, save_path, self.image_extension)

        self.paths = list(Path(save_path).glob("*" + self.image_extension))

if __name__ == "__main__":

    from torchvision import transforms

    data_transform = transforms.Compose(
        [
            # Resize and Crop the images to 640x640
            transforms.Resize(EyeQ.image_size),
            transforms.CenterCrop(EyeQ.image_size),
            # Turn the image into a torch.Tensor
            transforms.ToTensor(),  # this also converts all pixel values from 0 to 255 to be between 0.0 and 1.0
            transforms.Normalize(EyeQ.mean, EyeQ.std),
        ]
    )

    # Setup path to data folder
    data_path = Path("~/.torchvision/")
    image_path = data_path / "eyeq"

    split = "train"

    dataset = EyeQ(image_path, split=split, transform=data_transform)  # train_data
    n = len(dataset)
    print(f"EyeQ, split {split}, has {n} samples.")

    ################################################################################################

    import matplotlib.pyplot as plt
    import torchvision

    def display_image(
        dataset: torchvision.datasets.VisionDataset, targ_sample: int
    ) -> None:
        targ_image, targ_label = dataset[targ_sample][0], dataset[targ_sample][1]

        # Adjust image tensor shape for plotting (rearrange the order of dimensions)
        # [color_channels, height, width] -> [height, width, color_channels]
        targ_image_adjust = (
            targ_image.permute(1, 2, 0) if torch.is_tensor(targ_image) else targ_image
        )

        # Setup plot
        plt.imshow(targ_image_adjust)
        plt.axis("off")
        title = f"class: {EyeQ.classes_names[targ_label]}"
        if torch.is_tensor(targ_image_adjust):
            title = title + f"\nshape: {targ_image_adjust.shape}"
        plt.title(title)

        # Plot the adjusted sample
        plt.show()

    display_image(dataset, 2)  # 13_left

    ################################################################################################

    from torch.utils.data import DataLoader

    train_dataloader_custom = DataLoader(
        dataset=dataset,  # use custom created train Dataset
        batch_size=1,  # how many samples per batch?
        num_workers=0,  # how many subprocesses to use for data loading? (higher = more)
        shuffle=True,  # shuffle the data?
    )

    # Get image and label from custom DataLoader
    img_custom = None
    while img_custom is None:
        try:
            img_custom, label_custom = next(iter(train_dataloader_custom))
        except (StopIteration, TypeError):
            break
        except:
            pass

    # Batch size will now be 1, try changing the batch_size parameter above and see what happens
    print(
        f"Image shape: {img_custom.shape} -> [batch_size, color_channels, height, width]"
    )
    print(f"Label shape: {label_custom.shape}")


import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import math
from scipy.ndimage import gaussian_filter

# Global Day Dictionary/Colormap
day_dict = {0: ('Sun.', 'r'), 1: ('Mon.', 'g'), 2: ('Tues.', 'b'), 3: ('Wed.', 'c'), 4: ('Thurs.', 'm'),
            5: ('Fri.', 'y'), 6: ('Sat.', 'k')}

def setup_data():
    """
    Imports data from csv, applies filters to remove outliers and manipulated data to facilitate further processing.
    :return: DataFrame holding the filtered/enhanced dataset.
    """
    data = pd.read_csv('partA/bikes_October18.csv', ',')

    # Outlier Removal
    data = remove_outliers(data)

    # Add Column to indicate the day of bike hire
    date = [int(i.split(' ')[0].split('-')[2]) for i in data['started_at'].tolist()]
    day = [i % 7 for i in date]
    data['day'] = day  # Add new col to dataFrame

    # Compute journey distance and add as a column
    distance = [haversine_distance((row['start_station_latitude'], row['start_station_longitude']),
                                   (row['end_station_latitude'], row['end_station_longitude']))
                for index, row in data.iterrows()]
    data['distance'] = distance

    # Calculate and store duration in mins
    duration_mins = [row['duration'] / 60 for index, row in data.iterrows()]
    data['duration_mins'] = duration_mins

    return data

def haversine_distance(p1, p2):
    """
    Computes the distance (in Km) between two points given their coordinates in Lat/Lng format. Uses Haversine formula.
    https://stackoverflow.com/questions/19412462/getting-distance-between-two-points-based-on-latitude-longitude/43211266
    :param p1: Tuple in (lat, lng) format for the 1st point
    :param p2: Tuple in (lat, lng) format for the 2nd point
    :return: Distance between the two points.
    """
    # approximate radius of earth in km
    r = 6373.0

    # Convert to radians
    lat1 = math.radians(p1[0])
    lon1 = math.radians(p1[1])
    lat2 = math.radians(p2[0])
    lon2 = math.radians(p2[1])

    dlon = lon2 - lon1
    dlat = lat2 - lat1

    a = math.sin(dlat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))

    return r * c

def remove_outliers(dirty_data):
    """
    Applies a set of filters to the data to remove outliers and extreme values.
    :param dirty_data: DataFrame holding the "dirty" data.
    :return: DataFrame holding the filtered data.
    """
    # Time filter - each daily bike journey cannot exceed 24*60*60 seconds
    data = dirty_data.drop(dirty_data[dirty_data.duration > 24 * 60 * 60].index)
    return data

def plot_daily_duration_distribution(data):
    """
    Make a BoxPlot of the duration of the hire to illustrate the distribution of journey duration each day.
    :param data: DataFrame holding all the data
    """

    # Plot daily duration distribution
    fig1, ax1 = plt.subplots()

    ax1.set_title('Journey Duration Distribution per day')  # Add plot title
    # Plot duration in mins
    ax1.boxplot([data.loc[data['day'] == i]['duration_mins'].tolist() for i in range(0, 7)], showfliers=False)
    ax1.set_xticklabels([val[0] for val in day_dict.values()], rotation='vertical')  # Add X-axis tick labels
    plt.ylabel('Duration (mins)')
    plt.show()

def scatter_duration_distance(data):
    """
    Creates scatter plot for duration (x) and distance (y) for all journeys in the dataset. Each color indicates a
    different day of the week.
    :param data: DataFrame holding all the data
    """
    distances = data['distance'].tolist()
    durations = data['duration'].tolist()
    colors = [day_dict[i][1] for i in data['day'].tolist()]

    plt.scatter(distances, durations, c=colors, s=2)
    plt.show()

def scatter_avg_duration_distance(data):
    """
    Creates scatter plot for average duration (x) and distance (y) for all journeys in the dataset.
    Each color indicates a different day of the week.
    :param data: DataFrame holding all the data
    """
    for i in range(0, 7):
        avg_duration = data.loc[data['day'] == i]['duration_mins'].mean()
        avg_distance = data.loc[data['day'] == i]['distance'].mean()

        plt.scatter(avg_distance, avg_duration, c=day_dict[i][1], label=day_dict[i][0])

    plt.title("Average Distance/Duration for each day")
    plt.xlabel("Average Distance (km)")
    plt.ylabel("Average Duration (min)")
    plt.legend()
    plt.show()

def get_stations(data):
    """
    Creates a dictionary (station_id as key) holding information (name, location, traffic) for all the stations in the
    dataset. Regarding the traffic, the number of bikes departing from a station in hourly base is saved as well as
    number of bikes departing and arriving in hourly base.
    :param data: DataFrame holding all the data
    :return: Dictionary of type {station_id: (station_name, (station_lat, station_lng), traffic arrive & depart [1 X 24]
    , traffic depart [1 X 24], traffic depart weekend [1 X 24])}
    """
    # Get all the station ids that are present in the dataset
    station_ids = list(set(data['start_station_id'].tolist() + data['end_station_id'].tolist()))

    station_dict = {}
    # Add Name - Location for each station
    for station_id in station_ids:
        if station_id in data['start_station_id']:
            lat_lng_name = data.loc[data['start_station_id'] == station_id][['start_station_latitude',
                                                                             'start_station_longitude',
                                                                             'start_station_name']].iloc[0].tolist()

        else:
            lat_lng_name = data.loc[data['end_station_id'] == station_id][['end_station_latitude',
                                                                           'end_station_longitude',
                                                                           'end_station_name']].iloc[0].tolist()

        station_dict[station_id] = (lat_lng_name[2], (lat_lng_name[0], lat_lng_name[1]), np.zeros(24), np.zeros(24),
                                    np.zeros(24))

    # Add hourly traffic for each station
    for index, row in data.iterrows():

        start_station_id = row['start_station_id']  # get station id
        start_time = int(row['started_at'].split(' ')[1].split(':')[0])  # get start time of journey

        # Add journey to traffic table
        start_station_val = station_dict[start_station_id]
        start_station_val[2][start_time] += 1

        if (row['day'] == 0) or (row['day'] == 6):
            start_station_val[4][start_time] += 1
        else:
            start_station_val[3][start_time] += 1

        end_station_id = row['end_station_id']  # get station id
        end_time = int(row['ended_at'].split(' ')[1].split(':')[0])  # get start time of journey

        # Add journey to traffic table
        end_station_val = station_dict[end_station_id]
        end_station_val[2][end_time] += 1

    return station_dict

def edinburgh_heat_map(stations):
    """
    Approximately converts station locations from lat/lng to XY and plots them. Then creates a heat map to illustrate
    the bike traffic density for different hours of the day.
    https://stackoverflow.com/questions/16266809/convert-from-latitude-longitude-to-x-y
    :param stations: Dictionary of type {station_id: (station_name, (station_lat, station_lng), traffic arrive & depart
    [1 X 24], traffic depart [1 X 24])}
    :return A tuple holding the traffic spatial time 3d matrix and a list of tuples of the form (name, x, y) where name
    is the name of the station and x, y is the location of the station on the XY grid.
    traffic_spatial_time: 3d matrix, where x, y is spatial grid and z is time (specifically 24, one for each
    time of the day.
    """
    # approximate radius of earth in km
    r = 6373.0

    # reference angle for Edinburgh
    phi = np.cos(55.943894)

    # number of stations
    n = len(stations.keys())

    # Convert Lat/Lng coordinates to XY coordinates
    station_dict_xy = {station_id: (stations[station_id][0], (r * stations[station_id][1][1] * phi,
                                                              r * stations[station_id][1][0]))
                       for station_id in stations.keys()}

    # Shift points chose to coordinate origin
    x, y = zip(*[el[1] for el in station_dict_xy.values()])
    mean_x = sum(x) / n
    mean_y = sum(y) / n
    station_dict_xy = {station_id: (station_dict_xy[station_id][0], (station_dict_xy[station_id][1][0] - mean_x,
                                                                     station_dict_xy[station_id][1][1] - mean_y))
                       for station_id in station_dict_xy.keys()}

    # Create traffic spatial map 3d - Each element indicates arrivals or departure in location (x, y) at time z
    x = x - mean_x
    y = y - mean_y
    granularity = 5
    im_x = math.ceil((max(x) - min(x)) / granularity)
    im_y = math.ceil((max(y) - min(y)) / granularity)

    traffic_spatial_time = np.zeros((im_x, im_y, 24))
    min_x = min(x)
    min_y = min(y)

    label_x_y = []
    for station_id in station_dict_xy.keys():
        # Find x, y for current station in traffic map
        cur_x = math.floor((station_dict_xy[station_id][1][0] - min_x) / granularity)
        cur_y = math.floor((station_dict_xy[station_id][1][1] - min_y) / granularity)

        label_x_y.append((stations[station_id][0], cur_x, cur_y))
        traffic_spatial_time[cur_x, cur_y, :] = stations[station_id][2]

    # Apply Gaussian filter (blur) for each time slice of the traffic_spatia_time matrix
    for h in range(24):
        traffic_spatial_time[:, :, h] = gaussian_filter(traffic_spatial_time[:, :, h], sigma=5)

    return traffic_spatial_time, label_x_y

def plot_edinburgh_heat_map(traffic_spatial_time, label_x_y, hour):
    """
    Plots the heat map for the given hour of the day.
    :param traffic_spatial_time: 3d matrix, where x, y is spatial grid and z is time (specifically 24, one for each
    time of the day.
    :param label_x_y: list of tuples of the form (name, x, y) where name is the name of the station and x, y is the
    location of the station on the XY grid
    :param hour: Integer specifying the hour of the day the plot will be about [0, 23]
    """
    # number of stations
    n = len(label_x_y)

    fig, ax = plt.subplots(figsize=(13, 8))
    ax.imshow(traffic_spatial_time[:, :, hour].transpose(), origin='lower')

    plt.title('Bike traffic at around %d:00' % hour)

    font = {'size': 8}
    plt.rc('font', **font)

    for ii in range(n):
        ax.annotate(label_x_y[ii][0], (label_x_y[ii][1], label_x_y[ii][2]))

    plt.show()

def station_graph_plot(stations, data):
    """
    Creates and plots the adjacency matrix, each station is a separate node and each edge is a journey.
    :param stations: Dictionary of type {station_id: (station_name, (station_lat, station_lng), traffic arrive & depart
    [1 X 24], traffic depart [1 X 24])}
    :param data: DataFrame holding all the data
    """
    station_names = [stations[station_id][0] for station_id in stations.keys()]

    # Get journeys made in Monday to Friday
    data_week = data.loc[(data['day'] == 0) & (data['day'] != 6)]
    # Get journeys made during the weekend
    data_weekend = data.loc[(data['day'] == 0) | (data['day'] == 6)]

    # Compute adjacency matrices for Week days and Weekend
    A_week = get_adjacency_matrix(data_week, station_names)
    A_weekend = get_adjacency_matrix(data_weekend, station_names)

    plot_adjacency_matrix(A_week, station_names, 'Adjacency matrix from Monday to Friday')
    plot_adjacency_matrix(A_weekend, station_names, 'Adjacency matrix for the Weekends')

def get_adjacency_matrix(data, nodes):
    """
    Creates the adjacency matrix
    :param data: DataFrame holding all the data
    :param nodes: List of strings as node labels (n)
    :return: Adjacency matrix
    """
    n = len(nodes)
    A = np.zeros((n, n))  # Init adjacency matrix

    for index, row in data.iterrows():

        start_station_name = row['start_station_name']  # get start station name
        end_station_name = row['end_station_name']  # get end station name
        start_index = nodes.index(start_station_name)
        end_index = nodes.index(end_station_name)
        A[start_index, end_index] += 1

    return A

def plot_adjacency_matrix(A, nodes, title):
    """
    Plots the adjacency matrix.
    :param A: Adjacency matrix (n -by- n)
    :param nodes: List of strings as node labels (n)
    :param title: Title for the plot
    """
    n = len(nodes)
    fig, ax = plt.subplots(figsize=(13, 8))
    ax.imshow(A)

    # Show all ticks
    ax.set_xticks(np.arange(n))
    ax.set_yticks(np.arange(n))
    # Node labels
    ax.set_xticklabels(nodes)
    ax.set_yticklabels(nodes)

    # Rotate the tick labels and set their alignment.
    plt.setp(ax.get_xticklabels(), rotation=90, ha="right",
             rotation_mode="anchor")

    # Loop over data dimensions and create text annotations.
    for i in range(n):
        for j in range(n):
            ax.text(j, i, int(A[i, j]), ha="center", va="center", color="w")

    ax.set_title(title)
    plt.show()

def plot_journey_start_time(stations):
    """
    Plot the absolute number of journeys started each hour of the day.
    :param stations: Dictionary of type {station_id: (station_name, (station_lat, station_lng), traffic arrive & depart
    [1 X 24], traffic depart [1 X 24])}
    """

    journey_per_hour = np.array([stations[station_id][3] for station_id in stations.keys()]).sum(axis=0)
    journey_per_hour = journey_per_hour / sum(journey_per_hour)

    journey_per_hour_weekend = np.array([stations[station_id][4] for station_id in stations.keys()]).sum(axis=0)
    journey_per_hour_weekend = journey_per_hour_weekend / sum(journey_per_hour_weekend)

    # Plot daily duration distribution
    fig1, ax1 = plt.subplots()
    ax1.set_xticks(np.arange(24))

    ax1.set_title('Journeys start per hour')  # Add plot title

    plt.plot(journey_per_hour, label='Monday to Friday')
    plt.plot(journey_per_hour_weekend, label='Weekend')

    plt.legend()
    plt.show()

# Main procedure
bike_data = setup_data()

# BoxPlots to illustrate the distribution of daily journey duration
plot_daily_duration_distribution(bike_data)

# Avg Scatter plot
scatter_avg_duration_distance(bike_data)

# Get station information
station_dict = get_stations(bike_data)

# Plot Bike stations on map
traffic_spatial_time, label_x_y = edinburgh_heat_map(station_dict)
plot_edinburgh_heat_map(traffic_spatial_time, label_x_y, 8)
plot_edinburgh_heat_map(traffic_spatial_time, label_x_y, 12)
plot_edinburgh_heat_map(traffic_spatial_time, label_x_y, 17)
plot_edinburgh_heat_map(traffic_spatial_time, label_x_y, 20)

# Plot station graph
station_graph_plot(station_dict, bike_data)

# What was the most common time(s) of day for journeys?
plot_journey_start_time(station_dict)