import psutil
import datetime
import logging
import time
import requests
from pythonosc.osc_server import ThreadingOSCUDPServer
from pythonosc.dispatcher import Dispatcher
from pythonosc.osc_bundle_builder import OscBundleBuilder
from pythonosc.osc_bundle_builder import IMMEDIATELY
from pythonosc.osc_message_builder import OscMessageBuilder
from pythonosc.udp_client import SimpleUDPClient

logger = logging.getLogger("root")

class cpuData(object):
    logger = logger.getChild("cpuData")
    @classmethod
    def get_data(cls):
        cls.logger.info("getting data")
        data = {}
        data["cpu"] = psutil.cpu_freq().current
        data["ram"] = psutil.virtual_memory().percent
        data = cls._construct_osc_bundle(data)
        return data

    @classmethod
    def _construct_osc_bundle(cls, data):
        bundle = OscBundleBuilder(IMMEDIATELY)
        for key, value in data.items():
            msg = OscMessageBuilder("/cpu/{}".format(key))
            msg.add_arg(value)
            bundle.add_content(msg.build())
        bundle = bundle.build()
        return bundle


class openWeatherData(object):
    logger = logger.getChild("openWeatherData")
    API_KEY = "4398785f60059433ff91c603e887f7ac"
    URL = "http://api.openweathermap.org/data/2.5/weather?"
    _data = {}

    @classmethod
    def _request_city_data(cls, city):
        url = "{}appid={}&q={}".format(
                cls.URL,
                cls.API_KEY,
                city,
                )
        data = requests.get(url).json()
        cls.logger.info(data)
        cls._data[city] = data
        return data

    @classmethod
    def get_data(cls, city):
        cls.logger.info("getting data")
        city = str(city).lower()
        if city not in cls._data.keys():
            cls.logger.info("requesting city: {}".format(city))
            cls._request_city_data(city)
            data = cls._construct_osc_bundle(city)
            return data
        else:
            cls.logger.info("retrieving city from internal database: {}".format(city))
            data = cls._construct_osc_bundle(city)
            return data

    @classmethod
    def _construct_osc_bundle(cls, city):
        bundle = OscBundleBuilder(IMMEDIATELY)
        for key, value in cls._data[city].items():
            if isinstance(value, int):
                msg = OscMessageBuilder("/weather/{}".format(key))
                msg.add_arg(value)
                bundle.add_content(msg.build())
            if isinstance(value, dict):
                for subkey, subvalue in value.items():
                    msg = OscMessageBuilder("/weather/{}/{}".format(key, subkey))
                    msg.add_arg(subvalue)
                    bundle.add_content(msg.build())
        bundle = bundle.build()
        return bundle

class bitcoinData(object):
    logger = logger.getChild("bitcoinData")
    URL = "https://api.coinmarketcap.com/v1/ticker/bitcoin/"

    @classmethod
    def get_data(cls):
        cls.logger.info("getting data")
        data = requests.get(cls.URL).json()
        data = cls._construct_osc_bundle(data)
        return data

    @classmethod
    def _construct_osc_bundle(cls, data):
        bundle = OscBundleBuilder(IMMEDIATELY)
        for element in data:
            for key, value in element.items():
                msg = OscMessageBuilder("/bitcoin/{}".format(key))
                msg.add_arg(value)
                bundle.add_content(msg.build())
        bundle = bundle.build()
        return bundle

class oscDataServer(object):
    def __init__(self, port=8080):
        self.logger = logger.getChild(self.__class__.__name__)
        self._dispatcher = Dispatcher()
        self._dispatcher.map("/get/cpu", self.get_cpu, needs_reply_address=True)
        self._dispatcher.map("/get/weather", self.get_city, needs_reply_address=True)
        self._dispatcher.map("/get/bitcoin", self.get_bitcoin, needs_reply_address=True)
        self._weather_data = openWeatherData()
        self._bitcoin_data = bitcoinData()
        self._cpu_data = cpuData()
        self._server = ThreadingOSCUDPServer(("0.0.0.0", port), self._dispatcher)
    
    def main(self):
        self.logger.info("starting server")
        self._server.serve_forever()

    def get_cpu(self, *args, **kwargs):
        data = self._cpu_data.get_data()
        client = SimpleUDPClient(args[0][0], port=8081)
        client.send(data)
        self.logger.info("data sent to {}".format(args[0][0]))

    def get_bitcoin(self, *args, **kwargs):
        data = self._bitcoin_data.get_data()
        client = SimpleUDPClient(args[0][0], port=8081)
        client.send(data)
        self.logger.info("data sent to {}".format(args[0][0]))

    def get_city(self, *args, **kwargs):
        data = self._weather_data.get_data(args[2])
        client = SimpleUDPClient(args[0][0], port=8081)
        client.send(data)
        self.logger.info("data sent to {}".format(args[0][0]))

def main():
    server = oscDataServer()
    server.main()

if __name__ == "__main__":
    logger.setLevel(logging.INFO)
    streamhandler = logging.StreamHandler()    
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    streamhandler.setFormatter(formatter)
    logger.addHandler(streamhandler)

    logger.info("hello world")

    main()

