# coding=utf-8
from __future__ import unicode_literals, print_function, absolute_import

import json
import os
import socket
import threading
import uuid

import kafka
import six
import time

from injection.data_sync.core.data import EventSource, EventHandler, DataSource


class ITracer(object):
    def send_periodicity(self, key, value_lambda):
        raise NotImplementedError

    def send(self, value):
        raise NotImplementedError


class EmptyTracer(ITracer):
    def send_periodicity(self, key, value_lambda):
        pass

    def send(self, value):
        pass


class KafkaTracer(ITracer):
    def __init__(
            self,
            brokers,
            topic,
            period_millis,
    ):
        assert isinstance(brokers, six.string_types)
        assert isinstance(topic, six.string_types)
        assert isinstance(period_millis, six.integer_types)
        self.__brokers = brokers
        self.__topic = topic
        self.__period_millis = period_millis

        self.__producer = kafka.KafkaProducer(
            bootstrap_servers=self.__brokers,
            client_id='{}.{}#{}'.format(
                self.__class__.__module__,
                self.__class__.__name__,
                uuid.uuid4().hex,
            ),
        )

        self.__lock = threading.Lock()
        self.__send_periodictly_dict = {}

        self.__hostname = socket.gethostname()
        self.__ppid = os.getppid()
        self.__pid = os.getpid()

        self.__worker_thread = threading.Thread(
            target=self.__period_worker,
        )
        self.__worker_thread.daemon = True
        self.__worker_thread.start()

    def __encode(self, value):
        decorated_value = {
            'encode_time': time.time(),
            'hostname': self.__hostname,
            'pid': self.__pid,
            'ppid': self.__ppid,
            'value': value,
        }
        return json.dumps(decorated_value, sort_keys=True).encode('ascii') + b'\n'

    def send_periodicity(self, key, value_lambda):
        d = self.__send_periodictly_dict
        if key in d:
            return
        value = value_lambda()
        with self.__lock:
            if key in d:
                return
            encoded_value = self.__encode(value)
            d[key] = encoded_value
            self.__send(encoded_value)

    def send(self, value):
        encoded_value = self.__encode(value)
        self.__send(encoded_value)

    def __send(self, encoded_value):
        self.__producer.send(
            self.__topic,
            encoded_value,
        )

    def __period_worker(self):
        while True:
            time.sleep(self.__period_millis / 1000.0)
            with self.__lock:
                encoded_values = list(self.__send_periodictly_dict.values())
            for v in encoded_values:
                self.__send(v)


_default_tracer = EmptyTracer()


def get_default_tracer():
    return _default_tracer


def set_default_tracer(tracer):
    global _default_tracer
    assert isinstance(tracer, ITracer)
    _default_tracer = tracer


class TracingEventHandler(EventHandler):
    def __init__(self, data_source, info):
        assert isinstance(data_source, DataSource)
        json.dumps(info)  # validate
        self.__info = info

        data_source.handler_set += self

    def process_event(self, event):
        trace_event_source(event.event_source)
        get_default_tracer().send({
            'tracing_event_handler': {
                'type': self.__class__.__name__,
                'info': self.__info,
                'event': event.to_json(),
            },
        })


def trace_event_source(event_source):
    assert isinstance(event_source, EventSource)
    get_default_tracer().send_periodicity(
        key=('event_source', event_source.id),
        value_lambda=lambda: {'event_source': event_source.to_json()}
    )
