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

# System modules
import telnetlib
import sys
import requests
from urllib.parse import urlencode
import re

# custom modules
from utils.logger import log
from devices import check


class OpenTSI:
    '''Base wrapper class for astelco OpenTSI stuff'''

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

        self.url = url.split(":")[0] or "localhost"
        self.port = url.split(":")[1] or 22
        self.timeout = 3
        self.error = None

    @check.telnet_errors
    def connect(self):
        '''Setup a telnet connection to the cabinet'''

        tn = telnetlib.Telnet(self.url, self.port, timeout=3)
        return tn

    @check.telnet_errors
    def send(self, get_or_set, message=None):
        '''
        Connect to the cabinet and send messages to set or get properties
        '''

        # Login data for cabinet communication.
        # It is a byte-string.
        auth = b'auth plain "admin" "admin"\n'

        # Send the message. it can be a string (even a multi-line
        # string), or a list of strings

        if type(message) is str:
            # Maybe is multi-line
            msglist = message.split("\n")
            # Removing empty elements
            msglist = [x for x in msglist if x]
        elif type(message) is list:
            msglist = message

        # GET: Transforming every string in "1 get string\n2 get string\n"
        # SET: Transforming every string in "1 set string=val\n2 set string=val"
        msg = [f"{i+1} {get_or_set} {j}\n" for i, j in enumerate(msglist)]
        # From list to string, and byte-coded
        msg = bytes("".join(msg), "utf+8")

        # Connect to the cabinet.
        tn = self.connect()
        if not tn:
            return {"Error": self.error}

        tn.read_until(b"[ruder]\n")
        tn.write(auth)
        tn.read_until(b"AUTH OK 0 0\n")

        # Send the message.
        tn.write(msg)

        # For each command, the *asyncrhronous* answer contains:
        #
        # COMMAND OK
        # DATA INLINE ...
        # COMMAND COMPLETE
        #
        # Append all the buffer line by line to a list.

        tn_answer = []
        for i in range(msg.count(b"\n")*3-1):
            tn_answer.append(tn.read_until(b"\n"))

        # Close the cabinet connection.
        tn.close()

        # Keep only the data.
        tn_values = [s for s in tn_answer if b"DATA INLINE" in s]

        # Create a python dictionary with name and value.
        telnet_dict = dict([m[14:-1].decode().split("=") for m in tn_values])

        # Try transforming values to float.
        for k, v in telnet_dict.items():
            try:
                telnet_dict[k] = int(v)
            except:
                try:
                    telnet_dict[k] = float(v)
                except ValueError:
                    telnet_dict[k] = str(v)

        return (telnet_dict)

    def get(self, message):
        '''Send a telnet message to the cabinet to retrieve a value or a set
        of values.'''

        return self.send("get", message)

    def put(self, key, val):
        '''Send to telnet a key to set a a value.
        keys and values can be both lists with the same element number.'''
        if type(key) is list:

            message = [f"{k}={v}" for k, v in zip(key, val)]
        else:
            message = f"{key}={val}"

        return self.send("set", message)


class Telescope(OpenTSI):
    '''Implementation of the Telescope commands mocking my Alpaca Telescope wrapper.'''

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

    def track(self):
        '''TBD with the right telnet commands'''

        # self.tracking = True
        # res = self.put("slewtotargetasync")

    def abort(self):
        '''TBD with the right telnet commands'''

        # res = self.put("abortslew")

    @property
    def is_moving(self):
        '''TBD with the right telnet commands'''

        # res = self.get("slewing")
        # self._is_moving = res
        # return self._is_moving

    @property
    def temperature(self):

        message = ["AUXILIARY.SENSOR[2].VALUE",
                   "AUXILIARY.SENSOR[3].VALUE",
                   "AUXILIARY.SENSOR[4].VALUE"]
        res = self.get(message)
        temps = [round(float(res[m]), 1)
                 for m in message]  # ordered by message

        return temps

    @property
    def status(self):
        message = "TELESCOPE.STATUS.LIST"
        res = self.get(message)
        return list(res.values())[0]

    @property
    def state(self):

        message = "TELESCOPE.STATUS.GLOBAL"
        res = self.get(message)
        return list(res.values())[0]

    def clear(self, n):

        res = self.put("TELESCOPE.STATUS.CLEAR_ERROR", n)
        return list(res.values())[0]

    @property
    def clock(self):
        """
        in UNIX time
        """

        message = "POSITION.LOCAL.UTC"
        res = self.get(message)
        return list(res.values())[0]

    @property
    def cover(self):

        message = "AUXILIARY.COVER.REALPOS"
        res = self.get(message)
        self._cover = list(res.values())[0]
        return self._cover

    @property
    def open(self):

        res = self.cover
        if res == 1.0:
            self._open = True
        elif res == 0.0:
            self._open = False
        else:
            self._open = None
        return self._open

    @open.setter
    def open(self, b):
        pos = 1.0 if b else 0.0
        res = self.put("AUXILIARY.COVER.TARGETPOS", pos)

        self._open = self.open

    @property
    def park(self):
        '''Get if the telescope is in parked position'''

    @park.setter
    def park(self, b, timeout=60):
        '''Init the telescope homing the axes, 
        or put the telescope in park position'''

    @property
    def altaz(self):

        message = ["POSITION.HORIZONTAL.ALT",
                   "POSITION.HORIZONTAL.AZ"]
        res = self.get(message)
        altaz = [res[m] for m in message]  # ordered by message
        self._altaz = altaz  # [alt, az]
        return self._altaz

    @altaz.setter
    def altaz(self, a):
        self.tracking = False
        keys = ["POSITION.HORIZONTAL.ALT",
                "POSITION.HORIZONTAL.AZ"]
        values = [a[0], a[1]]
        res = self.put(keys, values)
        self._altaz = self.altaz

    @property
    def targetradec(self):
        '''TBD with the right telnet commands'''

        # self._targetradec = [ra, dec]
        # return self._targetradec

    @targetradec.setter
    def targetradec(self, a):
        '''TBD with the right telnet commands'''

        # data = {"targetrightascension": a[0]}
        # ra = self.put("targetrightascension", data=data)
        # data = {"targetdeclination": a[1]}
        # dec = self.put("targetdeclination", data=data)
        self._targetradec = self.targetradec

    @property
    def radec(self):

        message = ["POSITION.EQUATORIAL.RA_J2000",
                   "POSITION.EQUATORIAL.DEC_J2000"]
        res = self.get(message)
        radec = [res[m] for m in message]  # ordered by message
        self._radec = radec
        return self._radec

    @radec.setter
    def radec(self, a):

        keys = ["POSITION.EQUATORIAL.RA_J2000",
                "POSITION.EQUATORIAL.DEC_J2000"]
        values = [a[0], a[1]]
        res = self.put(keys, values)
        # self.tracking = True
        self._radec = self.radec

    @property
    def offset(self):

        message = ["POSITION.INSTRUMENTAL.ZD.OFFSET",
                   "POSITION.INSTRUMENTAL.AZ.OFFSET"]
        res = self.get(message)
        offset = [res[m] for m in message]  # ordered by message
        self._offset = offset
        return self._offset

    @offset.setter
    def offset(self, a):
        keys = ["POSITION.INSTRUMENTAL.ZD.OFFSET",
                "POSITION.INSTRUMENTAL.AZ.OFFSET"]
        values = [a[0], a[1]]
        if abs(values[0]) > 0.1:  # 6 arcmin
            log.error(f"zd {zd} too large. Maybe arcsec instead of deg?")
            return
        if abs(values[1]) > 0.1:  # 6 arcmin
            log.error(f"az {az} too large. Maybe arcsec instead of deg?")
            return
        res = self.put(keys, values)
        self._offset = self.offset

    @property
    def tracking(self):
        '''TBD with the right telnet commands'''

        # res = self.get("tracking")
        # self._tracking = res
        # return self._tracking

    @tracking.setter
    def tracking(self, b):
        '''TBD with the right telnet commands'''

        # data = {"tracking": b}
        # res = self.put("tracking", data=data)
        # self._tracking = self.tracking

    @property
    def coordinates(self):
        """
        Ask simultaneously
        RA2000, DEC2000, ALT, AZ, LST, UTC.
        Answer is given as a dict of decimal numbers.
        UTC is given as unix time.
        """

        message = ["POSITION.EQUATORIAL.RA_J2000",
                   "POSITION.EQUATORIAL.DEC_J2000",
                   "POSITION.HORIZONTAL.ALT",
                   "POSITION.HORIZONTAL.AZ",
                   "POSITION.LOCAL.SIDEREAL_TIME",
                   "POSITION.LOCAL.UTC"]

        telnet_dict = self.get(message)

        # print(telnet_dict)

        if all(key in telnet_dict for key in message):
            self.error = []
            # radec and altaz in lists as usual
            simple_dict = {
                "radec": [telnet_dict["POSITION.EQUATORIAL.RA_J2000"],
                          telnet_dict["POSITION.EQUATORIAL.DEC_J2000"]],
                "altaz": [telnet_dict["POSITION.HORIZONTAL.ALT"],
                          telnet_dict["POSITION.HORIZONTAL.AZ"]],
                "utc": telnet_dict["POSITION.LOCAL.UTC"],
                "lst": telnet_dict["POSITION.LOCAL.SIDEREAL_TIME"],
            }
        else:
            simple_dict = {
                "radec": [None, None],
                "altaz": [None, None],
                "utc": None,
                "lst": None,
            }
            self.error.append("ASTELCO: Failed getting coordinates")

        return simple_dict

    #########
    # Tests
    #########

    def test1(self):
        message = "POSITION.EQUATORIAL.RA_J2000"
        return self.get(message)

    def test2(self):
        message = '''POSITION.EQUATORIAL.RA_J2000
        POSITION.EQUATORIAL.DEC_J2000
        POSITION.HORIZONTAL.alt
        POSITION.HORIZONTAL.az
        POSITION.LOCAL.SIDEREAL_TIME
        POSITION.LOCAL.UTC'''
        return self.get(message)

    def test3(self):
        message = ["POSITION.EQUATORIAL.RA_J2000",
                   "POSITION.EQUATORIAL.DEC_J2000",
                   "POSITION.HORIZONTAL.alt",
                   "POSITION.HORIZONTAL.az",
                   "POSITION.LOCAL.SIDEREAL_TIME",
                   "POSITION.LOCAL.UTC"]
        return self.get(message)
