#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Interface with a SBIG STX camera device
"""

# System modules
import time
from datetime import datetime
from urllib.parse import urlencode

# Third-party modules
import requests

# Other templates
from ..config.constants import temp_fits
from ..utils import check
from .basedevice import BaseDevice


class STX(BaseDevice):
    '''Base wrapper class for SBIG STX cameras.'''

    def __init__(self, url):
        '''Constructor.'''

        super().__init__(url)
        self.url = url
        self.addr = self.url
        self.timeout = 3
        self._last_command_time = 0
        self._command_interval = 0.05  # 50 milliseconds

    def _wait_if_needed(self):
        """
        Ensures the 50ms command interval is respected between commands.
        """
        elapsed = time.time() - self._last_command_time
        if elapsed < self._command_interval:
            time.sleep(self._command_interval - elapsed)
        self._last_command_time = time.time()

    @property
    def connection(self):
        '''Setup a telnet connection to the cabinet.
        No decorator to silence errors. If error, not connected.
        '''

        try:
            method = "VersionNumbers"
            res = requests.get(f"{self.addr}/{method}.cgi",
                               timeout=self.timeout)
            return True
        except Exception as e:
            self.error = ["No ping"]
            return False

    @check.request_errors
    def get(self, method, params=[]):
        '''Send a HTTP GET request to the device address.'''

        self._wait_if_needed()  # Wait before sending command
        res = requests.get(f"{self.addr}/{method}.cgi",
                           params="&".join(params),
                           timeout=self.timeout, verify=False)

        value = res.text.split("\r\n")
        if not value[-1]:
            value = value[:-1]
        if len(value) == 1:
            value = value[0]

        try:
            int(value)
            return int(value)
        except BaseException:
            try:
                float(value)
                return float(value)
            except BaseException:
                return value

    @check.request_errors
    def put(self, method, params={}):
        '''Send a HTTP GET request to the device address.'''

        self._wait_if_needed()  # Wait before sending command
        res = requests.get(f"{self.addr}/{method}.cgi",
                           params=urlencode(params),
                           timeout=self.timeout)

        res.raise_for_status()
        text = res.text.split("\r\n")
        if not text[-1]:
            text = text[:-1]
        if len(text) == 1:
            text = text[0]
        return text


class Camera(STX):
    '''Camera device based on STX cameras.'''

    def abort(self):
        self.put("ImagerAbortExposure")

    @check.request_errors
    def abort2(self):
        # https://github.com/dkirkby/STXLDriver/blob/master/stxldriver/camera.py
        # Maybe /exposure.html?Abort
        res = requests.get(f"exposure.html",
                           params=urlencode("Abort"),
                           timeout=self.timeout)

        res.raise_for_status()
        text = res.text.split("\r\n")
        if not text[-1]:
            text = text[:-1]
        if len(text) == 1:
            text = text[0]
        return text

    def start(
            self,
            duration,
            frametype,
            datetime=datetime.utcnow().isoformat()):
        params = {"Duration": duration,
                  "FrameType": frametype,
                  "DateTime": datetime}
        self.put("ImagerStartExposure", params=params)

    def download(self):
        res = requests.get(f"{self.addr}/Imager.FIT")
        with open(temp_fits, 'wb') as f:
            f.write(res.content)
        # return fits.ImageHDU.fromstring(res.content)

    def full_frame(self):
        maxx, maxy = self.max_range
        self.xrange = [0, maxx]
        self.yrange = [0, maxy]
        self.xystart = [0, 0]
        self.xyend = [maxx, maxy]
        return {"xrange": self.xrange, "yrange": self.yrange}

    def half_frame(self):
        maxx, maxy = self.max_range
        self.xrange = [maxx // 4 * 1, maxx // 4 * 3]
        self.yrange = [maxy // 4 * 1, maxy // 4 * 3]
        self.xystart = [maxx // 4, maxy // 4]
        self.xyend = [maxx // 4 * 3, maxy // 4 * 3]
        return {"xrange": self.xrange, "yrange": self.yrange}

    def small_frame(self):
        maxx, maxy = self.max_range
        self.xrange = [maxx // 20 * 9, maxx // 20 * 11]
        self.yrange = [maxy // 20 * 9, maxy // 20 * 11]
        self.xystart = [maxx // 20 * 9, maxy // 20 * 9]
        self.xyend = [maxx // 20 * 11, maxy // 20 * 11]
        return {"xrange": self.xrange, "yrange": self.yrange}

    @property
    def version(self):
        res = self.get("VersionNumbers")
        return res

    @property
    def description(self):
        res = self.get("Description")
        return res

    @property
    def state(self):
        res = self.get("ImagerState")
        return res

    @property
    def ready(self):
        res = self.get("ImagerImageReady")
        return res

    @property
    def all(self):
        params = ["AmbientTemperature",  # 0
                  "CCDTemperatureSetpoint",  # 1
                  "CCDTemperature",  # 2
                  "CoolerState",  # 3
                  "CoolerPower",  # 4
                  "BinX", "BinY",  # 5 #6
                  "CameraXSize", "CameraYSize",  # 7 #8
                  "StartX", "StartY",  # 9 #10
                  "NumX", "NumY"]  # 11 #12
        res = self.get("ImagerGetSettings", params=params)

        try:
            ambient, setpoint, temperature, cool, fan, binx, biny, camx, camy, startx, starty, numx, numy = list(
                map(float, res[:13]))
        except (TypeError, ValueError) as e:
            ambient, setpoint, temperature, cool, fan, binx, biny, camx, camy, startx, starty, numx, numy = [
                999, 999, 999, 0, 0, 999, 999, 0, 0, 0, 0, 0, 0]

        cooler = True if cool else False

        x_start = startx // binx
        y_start = starty // biny
        x_end = (startx + numx) // binx
        y_end = (starty + numy) // biny
        centx = camx // binx // 2
        centy = camy // biny // 2
        all_dict = {"ambient": round(ambient, 1),
                    "setpoint": round(setpoint, 1),
                    "temperature": round(temperature, 1),
                    "cooler": cooler,
                    "fan": round(fan, 0),
                    "binning": [binx, biny],
                    "max_range": [camx // binx, camy // biny],
                    "xystart": [x_start, y_start],
                    "xyend": [x_end, y_end],
                    "xrange": [x_start, x_end],
                    "yrange": [y_start, y_end],
                    "center": [centx, centy]}
        return all_dict

    @property
    def ambient(self):
        params = ["AmbientTemperature"]
        res = self.get("ImagerGetSettings", params=params)
        return round(res, 1)

    @property
    def max_range(self):
        params = ["CameraXSize", "CameraYSize"]
        res = self.get("ImagerGetSettings", params=params)
        if self.error:
            return None, None
        res = list(map(int, res))
        binning = self.binning
        return [r // b for r, b in zip(res, binning)]

    @property
    def is_moving(self):
        res = self.get("FilterState")
        self._is_moving = res
        return self._is_moving

    @property
    def filter(self):
        params = ["CurrentFilter"]  # , "CurrentFilterName"
        res = self.get("GetFilterSetting", params=params)
        self._filter = res
        return self._filter

    @filter.setter
    def filter(self, n):
        params = {"NewPosition": n}
        res = self.put("ChangeFilter", params=params)
        self._filter = self.filter

    @property
    def binning(self):
        params = ["BinX", "BinY"]
        res = self.get("ImagerGetSettings", params=params)
        if self.error:
            return None
        self.binning = list(map(int, res))
        return self._binning

    @binning.setter
    def binning(self, b):
        params = {"BinX": b[0], "BinY": b[1]}
        self.put("ImagerSetSettings", params=params)
        if self.error:
            return None
        self._binning = b

    @property
    def setpoint(self):
        params = ["CCDTemperatureSetpoint"]
        res = self.get("ImagerGetSettings", params=params)
        return res

    @property
    def temperature(self):
        params = ["CCDTemperature"]
        res = self.get("ImagerGetSettings", params=params)
        return round(res, 1)

    @temperature.setter
    def temperature(self, t):
        params = {"CCDTemperatureSetpoint": t}
        res = self.put("ImagerSetSettings", params=params)
        return self.setpoint

    @property
    def cooler(self):
        params = ["CoolerState"]
        res = self.get("ImagerGetSettings", params=params)
        self.cooler = True if res else False
        return self._cooler

    @cooler.setter
    def cooler(self, b):
        s = "1" if b else "0"
        params = {"CoolerState": s}
        res = self.put("ImagerSetSettings", params=params)
        self._cooler = b

    @property
    def fan(self):
        params = ["CoolerPower"]
        res = self.get("ImagerGetSettings", params=params)
        return round(res)

    @property
    def xrange(self):
        params = ["StartX", "NumX", "BinX"]  # start=20, width=100 (binning=1)
        res = self.get("ImagerGetSettings", params=params)
        if self.error:
            return None
        startx, numx, binx = list(map(int, res))
        # binx = self.binning[0] # binning is 2
        x_start = startx // binx
        x_end = numx // binx + x_start
        self.xrange = [x_start, x_end]  # start=10, end=60
        return self._xrange

    @xrange.setter
    def xrange(self, b):  # start=10, end=60
        binx = self.binning[0]  # binning is 2
        startx = b[0] * binx  # start: 10*2=20
        numx = b[1] * binx - startx  # width: 60*2-startx=100
        params = {"StartX": startx, "NumX": numx}
        res = self.put("ImagerSetSettings", params=params)
        self._xrange = b

    @property
    def yrange(self):
        params = ["StartY", "NumY", "BinY"]  # start=20, height=100 (binning=1)
        res = self.get("ImagerGetSettings", params=params)
        if self.error:
            return None
        starty, numy, biny = list(map(int, res))
        # biny = self.binning[1] # binning is 2
        y_start = starty // biny
        y_end = numy // biny + y_start
        self.yrange = [y_start, y_end]  # start=10, end=60
        return self._yrange

    @yrange.setter
    def yrange(self, b):  # start=10, end=60
        biny = self.binning[1]  # binning is 2
        starty = b[0] * biny  # start: 10*2=20
        numy = b[1] * biny - starty  # height: 60*2-starty=100
        params = {"StartY": starty, "NumY": numy}
        res = self.put("ImagerSetSettings", params=params)
        self._yrange = b

    @property
    def xystart(self):
        params = ["StartX", "StartY", "BinX", "BinY"]
        res = self.get("ImagerGetSettings", params=params)
        if self.error:
            return None
        startx, starty, binx, biny = list(map(int, res))
        x_start = startx // binx
        y_start = starty // biny
        self.xystart = [x_start, y_start]
        return self._xystart

    @xystart.setter
    def xystart(self, b):
        params = ["StartX", "StartY", "NumX", "NumY", "BinX", "BinY"]
        res = self.get("ImagerGetSettings", params=params)
        if self.error:
            return None
        startx, starty, numx, numy, binx, biny = list(map(int, res))
        numx = numx + startx - b[0] * binx
        numy = numy + starty - b[1] * biny
        startx = int(b[0]) * binx
        starty = int(b[1]) * biny
        params = {"StartX": startx, "StartY": starty,
                  "NumX": numx, "NumY": numy}
        res = self.put("ImagerSetSettings", params=params)
        self._xystart = b

    @property
    def xyend(self):
        params = ["StartX", "StartY", "NumX", "NumY", "BinX", "BinY"]
        res = self.get("ImagerGetSettings", params=params)
        if self.error:
            return None
        startx, starty, numx, numy, binx, biny = list(map(int, res))
        x_end = (startx + numx) // binx
        y_end = (starty + numy) // biny
        return [x_end, y_end]

    @xyend.setter
    def xyend(self, b):
        params = ["StartX", "StartY", "BinX", "BinY"]
        res = self.get("ImagerGetSettings", params=params)
        if self.error:
            return None
        startx, starty, binx, biny = list(map(int, res))
        numx = int(b[0]) * binx - startx
        numy = int(b[1]) * biny - starty
        params = {"NumX": numx, "NumY": numy}
        res = self.put("ImagerSetSettings", params=params)
        self._xyend = b

    @property
    def center(self):
        params = ["CameraXSize", "CameraYSize", "BinX", "BinY"]
        res = self.get("ImagerGetSettings", params=params)
        if self.error:
            return None
        camx, camy, binx, biny = list(map(int, res))
        centx = camx // binx // 2
        centy = camy // biny // 2
        return [centx, centy]
