# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import json
import logging
import os
import platform
import socket
import threading
import time
from collections import namedtuple
from contextlib import contextmanager

import trytond.config as config

status = dict()
logger = logging.getLogger(__name__)
address = 'trytond-stat.socket'

Process = namedtuple('Process', ['start_time', 'request'])

_PID = None
_LOCK = threading.Lock()


@contextmanager
def processing(request):
    start()  # check if running thread
    process = Process(time.perf_counter(), request)
    status[id(process)] = process
    try:
        yield
    finally:
        status.pop(id(process), None)


def log():
    from trytond.cache import Cache
    msg = []
    now = time.perf_counter()
    for process in sorted(status.copy().values(), key=lambda p: p.start_time):
        msg.append({
                'since': now - process.start_time,
                'request': str(process.request),
                })
    return {
        'id': '%s@%s' % (os.getpid(), platform.node()),
        'status': msg,
        'caches': list(Cache.stats()),
        }


def dump(path):
    if not hasattr(socket, 'AF_UNIX'):
        return False
    sock = socket.socket(socket.AF_UNIX)
    try:
        try:
            sock.connect(os.path.join(path, address))
        except socket.error:
            return False
        try:
            sock.sendall(json.dumps(log()).encode('utf-8'))
        except socket.error:
            pass
    finally:
        sock.close()
    return True


def dumper(path):
    while True:
        try:
            if dump(path):
                time.sleep(5)
            else:
                time.sleep(60)
        except Exception:
            logger.exception("status dumper crashed")
            time.sleep(60)


def start():
    global _PID
    if not hasattr(socket, 'AF_UNIX'):
        return
    if _PID != os.getpid():  # Quick test without lock
        with _LOCK:
            if _PID != os.getpid():
                path = config.get('database', 'path')
                threading.Thread(
                    target=dumper, args=(path,), daemon=True).start()
                _PID = os.getpid()


def listen(path, callback=None):
    if not hasattr(socket, 'AF_UNIX'):
        return False
    sock = socket.socket(socket.AF_UNIX)
    socket_file = os.path.join(path, address)
    try:
        sock.bind(socket_file)
        sock.listen(1)
        sock.settimeout(5)
        while True:
            try:
                connection, client_address = sock.accept()
            except socket.timeout:
                if callback:
                    callback()
                continue
            try:
                data = b''
                while True:
                    chunk = connection.recv(1024)
                    if not chunk:
                        break
                    else:
                        data += chunk
                try:
                    data = json.loads(data)
                except ValueError:
                    continue
                if callback:
                    callback(data)
            finally:
                connection.close()
    finally:
        sock.close()
        os.unlink(socket_file)
    return True
