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

# System modules
from astropy.time import Time
from time import sleep

# Custom modules
from utils.logger import log
from config.constants import filter_name, filter_number, filter_state
from config.constants import frame_type, frame_number, camera_state
from config.constants import temp_fits
from devices import cam, ast

# Other templates
from templates.basetemplate import BaseTemplate
from templates.fillheader import Template as FillHeader
from templates.lampsoff import Template as LampsOff


class Template(BaseTemplate):
    def __init__(self):
        super().__init__()
        self.name = "observation"
        self.description = "Observes a field"

    def content(self, params):

        ########################
        ##### Params check #####
        ########################

        try:
            objname = params.get("object") or "test"
            filt = params["filter"]
            binning = params["binning"]
            repeat = params.get("repeat") or 1
            exptime = params["exptime"]
            frametype = params.get("frametype") or "Light"
            xystart = params.get("xystart")
            xyend = params.get("xyend")

        except KeyError as e:
            log.error(f"Parameter {e} not found")
            return

        #########################################
        ##### Switch off lamps if not Flat  #####
        #########################################

        if frametype != "Flat":
            lao = LampsOff()
            lao.run({})

        ########################
        ##### Camera setup #####
        ########################

        # Bringing back the camera to Idle state.
        if cam.state != 0:
            log.warning(
                f"Resetting camera from state {camera_state[cam.state]}.")
            cam.abort()
            sleep(0.1)

        log.info(f"Camera is {camera_state[cam.state]}")

        # Changing binning if not the same as the current one
        if binning != cam.binning:
            log.info(f"Changing binning to {binning}")
            cam.binning = [binning, binning]
            sleep(0.1)

        log.info(f"Camera is {camera_state[cam.state]}")

        # Changing filter if not the same as the current one
        # On bad ctrl-c, filter can be 15 (?!)
        log.warning(f"Filter is number {cam.filter}")
        log.warning(
            f"Filter is {filter_name[cam.filter]}, we have selected {filt}")

        if filter_number[filt] != cam.filter:
            msg = f"Changing filter to {filt}, "
            msg += f"Which is number {filter_number[filt]}"
            log.info(msg)

            # Setting filter based on its number
            cam.filter = filter_number[filt]
            sleep(0.2)

            # Check until filter is not moving
            while cam.is_moving != 0:
                status = cam.is_moving
                if status == 2:  # i.e. error in moving filter
                    log.error(f"Filter {filter_state[status]}")
                    return
                else:
                    log.warning(f"Filter {filter_state[status]}")
                    sleep(0.5)

        # Changing windowing
        if xystart and xyend:
            log.info(
                f"Changing windowding to xystart:{xystart}, xyend:{xyend}")
            cam.xystart = xystart
            cam.xyend = xyend
        else:
            msg = f"No windowing: full frame in bininng {binning}"
            log.warning(msg)
            cam.full_frame()

        #########################
        ##### Exposure loop #####
        #########################

        log.info(f"Starting {repeat} {frametype} frames of {exptime}s")

        # Looping on the number of exposures
        for exposure in range(0, repeat):

            # RA, DEC, ALT, AZ, UTC are asked to cabinet
            # simultaneously in order to avoid latency.

            log.info(f"Asking cabinet time and coordinates")
            basic_cabinet = ast.coordinates()
            gps = Time(ast.utc, format="unix")

            # Some debug time counter
            now = Time.now()

            log.info(f"Starting Exposure {exposure} of {repeat-1}")

            ##########################
            ##### Start exposure #####
            ##########################

            try:
                cam.start(exptime,
                          frame_number[frametype],
                          datetime=gps.isot)
            except Exception as e:
                log.error("An error occurred!")
                log.error(e)
                return

            # Checking if the exposure is still running
            while cam.state != 0:
                status = int(cam.state)
                if status == 5:  # i.e. error in camera
                    log.error(f"Camera {camera_state[status]}")
                    return
                else:
                    elapsed = (Time.now()-now).sec
                    msg = f"Camera {camera_state[status]}, {elapsed:.1f}/{exptime}s "
                    msg += f"of exposure {exposure}/{repeat-1}"
                    log.warning(msg)
                    sleep(0.5)

            ################################
            ##### Download from camera #####
            ################################

            log.info(f"Downloading frame as {temp_fits}")
            cam.download()
            log.info(f"Elapsed {(Time.now()-now).sec:.2f}")

            ##################################
            ##### Update header and save #####
            ##################################

            fih_params = {"object": objname}
            fih_params.update(basic_cabinet)

            fih = FillHeader()
            fih.run(fih_params)

        return
