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

# System modules
import requests
from astropy.time import Time
from urllib.parse import urlencode

# Custom modules
from config.constants import temp_fits
from utils.logger import log
from devices import check


class Camera:
    '''Base wrapper class for SBIG STX cameras.'''

    def __init__(self, url):
        '''Constructor.'''
        self.url = url
        self.addr = self.url
        self.timeout = 3
        self.error = []

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

        res = requests.get(f"{self.addr}/{method}.cgi",
                           params="&".join(params),
                           timeout=self.timeout)
        # res.raise_for_status()
        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:
            try:
                float(value)
                return float(value)
            except:
                return value

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

        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

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

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

    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, maxx//4*3]
        self.yrange = [maxy//4, 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*19, maxx//20*21]
        self.yrange = [maxy//20*19, maxy//20*21]
        self.xystart = [maxx//20*19, maxy//20*19]
        self.xyend = [maxx//20*21, maxy//20*21]
        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",
                  "CCDTemperatureSetpoint",
                  "CCDTemperature",
                  "CoolerState",
                  "CoolerPower",
                  "CameraXSize",
                  "CameraYSize",
                  "BinX", "BinY",
                  "StartX", "StartY",
                  "NumX", "NumY"]
        res = self.get("ImagerGetSettings", params=params)

        all_dict = dict(zip(params, res))

        return all_dict

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

    @property
    def max_range(self):
        params = ["CameraXSize", "CameraYSize"]
        res = self.get("ImagerGetSettings", params=params)
        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 res

    @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 xrange(self):
        params = ["StartX", "NumX", "BinX"]  # start=20, width=100 (binning=1)
        res = self.get("ImagerGetSettings", params=params)
        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)
        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)
        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)
        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)
        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)
        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)
        camx, camy, binx, biny = list(map(int, res))
        centx = camx//binx//2
        centy = camy//biny//2
        return [centx, centy]
