# -*- coding: utf-8 -*-
"""A minimal non-colored version of https://pypi.org/project/halo, to track list progress"""
from __future__ import absolute_import, unicode_literals

import os
import sys
import threading
from collections import OrderedDict
from datetime import datetime

import py

threads = []

if os.name == "nt":
    import ctypes

    class _CursorInfo(ctypes.Structure):
        _fields_ = [("size", ctypes.c_int), ("visible", ctypes.c_byte)]


def _file_support_encoding(chars, file):
    encoding = getattr(file, "encoding", None)
    if encoding is not None:
        for char in chars:
            try:
                char.encode(encoding)
            except UnicodeEncodeError:
                break
        else:
            return True
    return False


class Spinner(object):
    CLEAR_LINE = "\033[K"
    max_width = 120
    UNICODE_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
    ASCII_FRAMES = ["|", "-", "+", "x", "*"]

    def __init__(self, enabled=True, refresh_rate=0.1):
        self.refresh_rate = refresh_rate
        self.enabled = enabled
        self._file = sys.stdout
        self.frames = (
            self.UNICODE_FRAMES
            if _file_support_encoding(self.UNICODE_FRAMES, sys.stdout)
            else self.ASCII_FRAMES
        )
        self.stream = py.io.TerminalWriter(file=self._file)
        self._envs = OrderedDict()
        self._frame_index = 0

    def clear(self):
        if self.enabled:
            self.stream.write("\r")
            self.stream.write(self.CLEAR_LINE)

    def render(self):
        while True:
            self._stop_spinner.wait(self.refresh_rate)
            if self._stop_spinner.is_set():
                break
            self.render_frame()
        return self

    def render_frame(self):
        if self.enabled:
            self.clear()
            self.stream.write("\r{}".format(self.frame()))

    def frame(self):
        frame = self.frames[self._frame_index]
        self._frame_index += 1
        self._frame_index = self._frame_index % len(self.frames)
        text_frame = "[{}] {}".format(len(self._envs), " | ".join(self._envs))
        if len(text_frame) > self.max_width - 1:
            text_frame = "{}...".format(text_frame[: self.max_width - 1 - 3])
        return "{} {}".format(*[(frame, text_frame)][0])

    def __enter__(self):
        if self.enabled:
            self.disable_cursor()
        self.render_frame()
        self._stop_spinner = threading.Event()
        self._spinner_thread = threading.Thread(target=self.render)
        self._spinner_thread.setDaemon(True)
        self._spinner_thread.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if not self._stop_spinner.is_set():
            if self._spinner_thread:
                self._stop_spinner.set()
                self._spinner_thread.join()

            self._frame_index = 0
            if self.enabled:
                self.clear()
                self.enable_cursor()

        return self

    def add(self, name):
        self._envs[name] = datetime.now()

    def succeed(self, key):
        self.finalize(key, "✔ OK", green=True)

    def fail(self, key):
        self.finalize(key, "✖ FAIL", red=True)

    def skip(self, key):
        self.finalize(key, "⚠ SKIP", white=True)

    def finalize(self, key, status, **kwargs):
        start_at = self._envs[key]
        del self._envs[key]
        if self.enabled:
            self.clear()
        self.stream.write(
            "{} {} in {}{}".format(
                status, key, td_human_readable(datetime.now() - start_at), os.linesep,
            ),
            **kwargs
        )
        if not self._envs:
            self.__exit__(None, None, None)

    def disable_cursor(self):
        if self._file.isatty():
            if os.name == "nt":
                ci = _CursorInfo()
                handle = ctypes.windll.kernel32.GetStdHandle(-11)
                ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci))
                ci.visible = False
                ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci))
            elif os.name == "posix":
                self.stream.write("\033[?25l")

    def enable_cursor(self):
        if self._file.isatty():
            if os.name == "nt":
                ci = _CursorInfo()
                handle = ctypes.windll.kernel32.GetStdHandle(-11)
                ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci))
                ci.visible = True
                ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci))
            elif os.name == "posix":
                self.stream.write("\033[?25h")


def td_human_readable(delta):
    seconds = int(delta.total_seconds())
    periods = [
        ("year", 60 * 60 * 24 * 365),
        ("month", 60 * 60 * 24 * 30),
        ("day", 60 * 60 * 24),
        ("hour", 60 * 60),
        ("minute", 60),
        ("second", 1),
    ]

    texts = []
    for period_name, period_seconds in periods:
        if seconds > period_seconds or period_seconds == 1:
            period_value, seconds = divmod(seconds, period_seconds)
            if period_name == "second":
                ms = delta.total_seconds() - int(delta.total_seconds())
                period_value = round(period_value + ms, 3)
            has_s = "s" if period_value != 1 else ""
            texts.append("{} {}{}".format(period_value, period_name, has_s))
    return ", ".join(texts)
