# Copyright (c) 2011 matt
# Copyright (c) 2011 Paul Colomiets
# Copyright (c) 2011-2014 Tycho Andersen
# Copyright (c) 2012 dmpayton
# Copyright (c) 2012 hbc
# Copyright (c) 2012 Tim Neumann
# Copyright (c) 2012 uberj
# Copyright (c) 2012-2013 Craig Barnes
# Copyright (c) 2013 Tao Sauvage
# Copyright (c) 2014 Sean Vig
# Copyright (c) 2014 dequis
# Copyright (c) 2014 Sebastien Blot
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import os
import platform
import re
import warnings
from abc import ABC, abstractclassmethod
from enum import Enum, unique
from pathlib import Path
from subprocess import CalledProcessError, check_output
from typing import Any, Dict, List, NamedTuple, Optional, Tuple

from libqtile import bar, configurable, images
from libqtile.images import Img
from libqtile.log_utils import logger
from libqtile.utils import send_notification
from libqtile.widget import base


@unique
class BatteryState(Enum):
    CHARGING = 1
    DISCHARGING = 2
    FULL = 3
    EMPTY = 4
    UNKNOWN = 5


BatteryStatus = NamedTuple("BatteryStatus", [
    ("state", BatteryState),
    ("percent", float),
    ("power", float),
    ("time", int),
])


class _Battery(ABC):
    """
        Battery interface class specifying what member functions a
        battery implementation should provide.
    """
    @abstractclassmethod
    def update_status(self) -> BatteryStatus:
        """Read the battery status

            Reads the battery status from the system and returns the result.

            Raises RuntimeError on error.
        """
        pass


def load_battery(**config) -> _Battery:
    """Default battery loading function

    Loads and returns the _Battery interface suitable for the current running
    platform.

    Parameters
    ----------
    config: Dictionary of config options that are passed to the generated
    battery.

    Return
    ------
    The configured _Battery for the current platform.
    """
    system = platform.system()
    if system == 'FreeBSD':
        return _FreeBSDBattery(str(config["battery"]))
    elif system == 'Linux':
        return _LinuxBattery(**config)
    else:
        raise RuntimeError('Unknown platform!')


class _FreeBSDBattery(_Battery):
    """
        A battery class compatible with FreeBSD. Reads battery status
        using acpiconf.

        Takes a battery setting containing the number of the battery
        that should be monitored.
    """

    def __init__(self, battery='0') -> None:
        self.battery = battery

    def update_status(self) -> BatteryStatus:
        try:
            info = check_output(['acpiconf', '-i', self.battery]).decode('utf-8')
        except CalledProcessError:
            raise RuntimeError('acpiconf exited incorrectly')

        stat_match = re.search(r'State:\t+([a-z]+)', info)

        if stat_match is None:
            raise RuntimeError('Could not get battery state!')

        stat = stat_match.group(1)
        if stat == 'charging':
            state = BatteryState.CHARGING
        elif stat == 'discharging':
            state = BatteryState.DISCHARGING
        elif stat == 'high':
            state = BatteryState.FULL
        else:
            state = BatteryState.UNKNOWN

        percent_re = re.search(r'Remaining capacity:\t+([0-9]+)', info)
        if percent_re:
            percent = int(percent_re.group(1)) / 100
        else:
            raise RuntimeError('Could not get battery percentage!')

        power_re = re.search(r'Present rate:\t+(?:[0-9]+ mA )*\(?([0-9]+) mW',
                             info)
        if power_re:
            power = float(power_re.group(1)) / 1000
        else:
            raise RuntimeError('Could not get battery power!')

        time_re = re.search(r'Remaining time:\t+([0-9]+:[0-9]+|unknown)',
                            info)
        if time_re:
            if time_re.group(1) == 'unknown':
                time = 0
            else:
                hours, _, minutes = time_re.group(1).partition(':')
                time = int(hours) * 3600 + int(minutes) * 60
        else:
            raise RuntimeError('Could not get remaining battery time!')

        return BatteryStatus(state, percent=percent, power=power, time=time)


class _LinuxBattery(_Battery, configurable.Configurable):
    defaults = [
        (
            'status_file',
            None,
            'Name of status file in /sys/class/power_supply/battery_name'
        ),