# -*- mode: python; coding: utf-8 -*-
# Copyright 2017 the HERA Collaboration
# Licensed under the 2-clause BSD license.

"""Handling quality metrics of HERA data."""

from math import floor

from astropy.time import Time
from sqlalchemy import BigInteger, Column, Float, ForeignKey, Integer, String
from sqlalchemy.ext.hybrid import hybrid_property

from . import DEFAULT_GPS_TOL, MCDeclarativeBase


class AntMetrics(MCDeclarativeBase):
    """
    Definition of ant_metrics table.

    These are metrics, generally generated by
    hera_qm, which are keyed to individual antennas. For example, hera_qm.ant_metrics
    will flag individual antennas as bad.

    Attributes
    ----------
    obsid : BigInteger Column
        observation identification number, generally equal to the floor
        of the start time in gps seconds (long integer). ForeignKey into hera_obs table.
    ant : Integer Column
        Antenna number (int >= 0)
    pol : String Column
        Polarization ('x', 'y', 'n', 'e', 'jnn', or 'jee')
    metric : String Column
        Name of metric
    mc_time : BigInteger Column
        time metric is reported to M&C in floor(gps seconds)
    val : Float Column
        Value of metric (double)

    """

    __tablename__ = "ant_metrics"
    obsid = Column(BigInteger, ForeignKey("hera_obs.obsid"), primary_key=True)
    ant = Column(Integer, primary_key=True, index=True)
    pol = Column(String, primary_key=True, index=True)
    metric = Column(
        String, ForeignKey("metric_list.metric"), primary_key=True, index=True
    )
    mc_time = Column(BigInteger, nullable=False)
    val = Column(Float, nullable=False)

    # tolerances set to 1ms
    tols = {"mc_time": DEFAULT_GPS_TOL}

    @hybrid_property
    def antpol(self):
        """
        Get antenna number and pol as a tuple.

        Returns
        -------
        tuple
            (ant, pol)
        """
        return (self.ant, self.pol)

    @classmethod
    def create(cls, obsid, ant, pol, metric, db_time, val):
        """
        Create a new ant_metric object using Astropy to compute the LST.

        Parameters
        ----------
        obsid: long integer
            observation identification number.
        ant: integer
            antenna number
        pol: string ('x', 'y', 'n', 'e', 'jnn', or 'jee')
            polarization
        metric: string
            metric name
        db_time: astropy time object
            astropy time object based on a timestamp from the database.
            Usually generated from MCSession.get_current_db_time()
        val: float
            value of metric

        """
        if not isinstance(obsid, int):
            raise ValueError("obsid must be an integer.")
        if not isinstance(ant, int):
            raise ValueError("antenna must be an integer.")
        pol = str(pol).lower()
        if pol not in ("x", "y", "n", "e", "jnn", "jee"):
            raise ValueError(
                f'pol={pol} not a string in ("x", "y", "n", "e", "jnn", "jee").'
            )
        if not isinstance(metric, str):
            raise ValueError("metric must be string.")
        if not isinstance(db_time, Time):
            raise ValueError("db_time must be an astropy Time object")
        mc_time = floor(db_time.gps)
        try:
            val = float(val)
        except ValueError as err:
            raise ValueError("val must be castable as float.") from err

        return cls(
            obsid=obsid, ant=ant, pol=pol, metric=metric, mc_time=mc_time, val=val
        )


class ArrayMetrics(MCDeclarativeBase):
    """
    Definition of array_metrics table.

    These are metrics, generally generated by
    hera_qm, which are keyed to the overall array. For example, hera_qm.firstcal_metrics
    generates an overall decision whether the firstcal solutions were "good."

    Attributes
    ----------
    obsid : BigInteger Column
        observation identification number, generally equal to the floor
        of the start time in gps seconds (long integer). ForeignKey into hera_obs table.
    metric : String Column
        Name of metric
    mc_time : BigInteger Column
        time metric is reported to M&C in floor(gps seconds)
    val : Float Column
        Value of metric (double)

    """

    __tablename__ = "array_metrics"
    obsid = Column(BigInteger, ForeignKey("hera_obs.obsid"), primary_key=True)
    metric = Column(String, ForeignKey("metric_list.metric"), primary_key=True)
    mc_time = Column(BigInteger, nullable=False)
    val = Column(Float, nullable=False)

    # tolerances set to 1ms
    tols = {"mc_time": DEFAULT_GPS_TOL}

    @classmethod
    def create(cls, obsid, metric, db_time, val):
        """
        Create a new array_metric object using Astropy to compute the LST.

        Parameters
        ----------
        obsid: long integer
            observation identification number.
        metric: string
            metric name
        db_time: astropy time object
            astropy time object based on a timestamp from the database.
            Usually generated from MCSession.get_current_db_time()
        val: float
            value of metric

        """
        if not isinstance(obsid, int):
            raise ValueError("obsid must be an integer.")
        if not isinstance(metric, str):
            raise ValueError("metric must be string.")
        if not isinstance(db_time, Time):
            raise ValueError("db_time must be an astropy Time object")
        mc_time = floor(db_time.gps)
        try:
            val = float(val)
        except ValueError as err:
            raise ValueError("val must be castable as float.") from err

        return cls(obsid=obsid, metric=metric, mc_time=mc_time, val=val)


class MetricList(MCDeclarativeBase):
    """
    Definition of metric_list table, which provides descriptions of metrics.

    Attributes
    ----------
    metric : String Column
        Name of metric
    desc : String Column
        Description of metric

    """

    __tablename__ = "metric_list"
    metric = Column(String, primary_key=True)
    desc = Column(String, nullable=False)

    @classmethod
    def create(cls, metric, desc):
        """
        Create a new ant_metric object using Astropy to compute the LST.

        Parameters
        ----------
        metric: string
            metric name
        desc: string
            description of metric

        """
        if not isinstance(metric, str):
            raise ValueError("metric must be string.")
        if not isinstance(desc, str):
            raise ValueError("metric description must be a string.")

        return cls(metric=metric, desc=desc)
