#!/usr/bin/python3
#
# Copyright (c) 2021, AT&T Intellectual Property.  All Rights Reserved
#
# SPDX-License-Identifier: LGPL-2.1-only

import subprocess
import vci
import timing_utility
from CPLD_utility import CPLDUtility
import json
from threading import Lock

DPLL1 = 1  # Phase
DPLL2 = 2  # Frequency
DPLL3 = 3  # Timestamp
AUTO = 0
V1_SYSTEM = "vyatta-system-v1:system"
V1_TIMING = "vyatta-system-timing-v1:timing"
SUPPORTED_PLATFORMS = ["ufi.s9500-30xs"]   # add new platform here

timing_source_config = {
    "one-pps": {
        "gps-1pps": {"weighted-priority": 50},
        "ptp-1pps": {"weighted-priority": 40},
        "sma-1pps": {"weighted-priority": 30},
        "tod-1pps": {"weighted-priority": 20},
    },
    "frequency": {
        "gps":      {"weighted-priority": 50},
        "synce":    {"weighted-priority": 40},
        "ptp":      {"weighted-priority": 30},
        "sma":      {"weighted-priority": 20},
        "bits":     {"weighted-priority": 10},
    },
}
default_timing_source_config = timing_source_config.copy()

def timing_supported():
    platform = subprocess.run(["/opt/vyatta/bin/vyatta-platform-util", "--what-am-i"],
                              check=True, stdout=subprocess.PIPE,
                              universal_newlines=True).stdout.rstrip("\n")
    return platform in SUPPORTED_PLATFORMS

def src_name_from_dpll1(dpll1_name):
    return dpll1_name  # same to name from command line


def src_name_from_dpll2(dpll2_name):
    dpll2_to_src_name = {
        "GPS-10MHz": "gps",
        "SyncE-BCM82398-100G-PIN1": "synce",
        "SyncE-BCM82398-100G-PIN2": "synce",
        "SyncE-BCM82780-10G": "synce",
        "SyncE-BCM88470-10G": "synce",
        "PTP-10MHz": "ptp",
        "SMA-10MHz": "sma",
        "BITS": "bits",
    }
    if dpll2_name not in dpll2_to_src_name:
        return "None"
    return dpll2_to_src_name[dpll2_name]


def sorted_by_weighted_priority(items):
    return sorted(items, key=lambda item: item["weighted-priority"], reverse=True)


class Config(vci.Config):
    def __init__(self, *args, **kwargs):
        global timing_source_config
        super().__init__(*args, **kwargs)
        self.timing_util = timing_utility.TimingUtility()
        self.cpld = CPLDUtility()
        self.timing_source_config = timing_source_config
        self.tod_output_enabled = False
        self.mutex = Lock()
        # dpll initialization
        self.timing_util.set_dpll_op_mode(DPLL1, AUTO)
        self.timing_util.set_dpll_op_mode(DPLL2, AUTO)
        self.timing_util.set_dpll_op_mode(DPLL3, AUTO)
        # config_file_path = '/etc/vyatta/vyatta-system-timing.json'

    def enable_tod_output(self, enable):
        self.tod_output_enabled = enable
        self.cpld.set_tod_output(1 if enable else 0)

    def set_config(self, cmd_dict, cmd_type):
        """set config into self.timing_source_config

        Args:
            cmd_dict (dict): command data structure
            cmd_type (string): command type, like one-pps, frequency
        """
        for src_type,value in cmd_dict[cmd_type].items():
            self.timing_source_config[cmd_type][src_type][
                'weighted-priority'] = value["weighted-priority"]

    def set(self, input):
        """set function of Config class

        Args:
            input (dict):dictionary from vci core, example of input:
                {'vyatta-system-v1:system':
                    {'vyatta-system-timing-v1:timing':
                        {'timing-source':
                            {'one-pps':
                                'gps-1pps': {'weighted-priority': 20},
                            }
                        }
                    }
                }
        """
        print(f"set config {input}", flush=True)
        if not input:
            input = {
                V1_SYSTEM: {
                    V1_TIMING:{}
                }
            }
        timingObj = input[V1_SYSTEM][V1_TIMING]
        # do something to realize the configuration on the system
        self.mutex.acquire()
        cmd_dict = default_timing_source_config
        if "timing-source" in timingObj:
            cmd_dict = timingObj["timing-source"]
        tod_out = False
        if "tod-output" in timingObj and timingObj["tod-output"]:
            tod_out = True
        try:
            self.enable_tod_output(tod_out)
        except:
            print(f"Failed to call enable_tod_output({tod_out})", flush=True)
            self.mutex.release()
            return
        if "one-pps" in cmd_dict:
            self.set_config(cmd_dict, "one-pps")  # save config into self.timing_source_config
            # sort self.timing_source_config
            one_pps_list = [{"src_name":src, "priority":value["weighted-priority"]}
                            for src,value in self.timing_source_config["one-pps"].items()]
            one_pps_list = sorted(one_pps_list, key=lambda item: item["priority"], reverse=True)
            # let's config all 1PPS items to BSP, to avoid priority order issue.
            for i in range(len(one_pps_list)):
                src_name = one_pps_list[i]["src_name"]
                src_name = 'ToD-1PPS' if src_name == "tod-1pps" else src_name.upper()
                real_priority = 1 + i  # priority starts from 1
                print(f"set_1pps_priority:{src_name}, {real_priority}", flush=True)
                self.timing_util.set_1pps_priority(src_name, real_priority)
            self.timing_util.set_dpll_fast_lock(DPLL1)
            self.timing_util.set_dpll_op_mode(DPLL1, AUTO)
        if "frequency" in cmd_dict:
            self.set_config(cmd_dict, "frequency")  # save config into self.timing_source_config
            # sort frequency list
            frequency_list = [{"src_name":src, "priority":value["weighted-priority"]}
                            for src,value in self.timing_source_config["frequency"].items()]
            frequency_list = sorted(frequency_list, key=lambda item: item["priority"], reverse=True)
            # let's config all 1PPS items to BSP, to avoid priority order issue.
            # config to BSP, since we might have 1->n configurations to BSP,
            # let's config all frequency items to BSP,
            # to avoid priority order issue.
            real_priority = 0
            real_priority3 = 0  # for dpll3 specifically
            for i in range(len(frequency_list)):
                src_name = frequency_list[i]["src_name"]
                if src_name in ["gps", "sma", "ptp"]:
                    real_priority += 1
                    print(
                        f"set_frequency_priority:{src_name.upper()}-10MHz, {real_priority}",
                        flush=True,
                    )
                    self.timing_util.set_frequency_priority(
                        f"{src_name.upper()}-10MHz", real_priority
                    )
                    if src_name != "ptp":  # no DPLL3 configuration for PTP
                        real_priority3 += 1  # for dpll3 specifically
                        print(
                            f"set_frequency_priority_dpll3:{src_name.upper()}-10MHz-DPLL3, {real_priority3}",
                            flush=True,
                        )
                        self.timing_util.set_frequency_priority_dpll3(
                            f"{src_name.upper()}-10MHz-DPLL3", real_priority3
                        )
                elif src_name == "bits":
                    real_priority += 1
                    print(f"set_frequency_priority: BITS, {real_priority}", flush=True)
                    self.timing_util.set_frequency_priority("BITS", real_priority)
                    real_priority3 += 1  # for dpll3 specifically
                    print(
                        f"set_frequency_priority_dpll3: BITS-DPLL3, {real_priority3}",
                        flush=True,
                    )
                    self.timing_util.set_frequency_priority_dpll3(
                        "BITS-DPLL3", real_priority3
                    )
                elif src_name == "synce":
                    for target_name in [
                        "SyncE-BCM82398-100G-PIN1",
                        "SyncE-BCM82398-100G-PIN2",
                        "SyncE-BCM82780-10G",
                        "SyncE-BCM88470-10G",
                    ]:
                        real_priority += 1
                        print(
                            f"set_frequency_priority: {target_name}, {real_priority}",
                            flush=True,
                        )
                        self.timing_util.set_frequency_priority(
                            target_name, real_priority
                        )
                        real_priority3 += 1  # for dpll3 specifically
                        print(
                            f"set_frequency_priority_dpll3: {target_name}, {real_priority3}",
                            flush=True,
                        )
                        self.timing_util.set_frequency_priority_dpll3(
                            target_name + "-DPLL3", real_priority3
                        )
            self.timing_util.set_dpll_fast_lock(DPLL2)
            self.timing_util.set_dpll_op_mode(DPLL2, AUTO)
            self.timing_util.set_dpll_fast_lock(DPLL3)
            self.timing_util.set_dpll_op_mode(DPLL3, AUTO)
        self.mutex.release()
        return

    def get(self):
        """get function for Config class

        Returns:
            dict format same to input of set() function
        """
        # return the configuration
        return {
            V1_SYSTEM: {
                V1_TIMING: {
                    "timing-source": self.timing_source_config,
                    "tod-output": self.tod_output_enabled,
                }
            }
        }

    def check(self, input):
        """check config

        Args:
            input (dict):dictionary from vci core, example of input:
                {'vyatta-system-v1:system':
                    {'vyatta-system-timing-v1:timing':
                        {'timing-source':
                            {'one-pps':
                                'gps-1pps': {'weighted-priority': 20},
                            }
                        }
                    }
                }
        """
        # do an additional configuration checks
        print(f"check config {input}", flush=True)
        if not input:
            return {
                V1_SYSTEM: {
                    V1_TIMING:{}
                }
            }
        return


class State(vci.State):
    def __init__(self, *args, **kwargs):
        global timing_source_config
        super().__init__(*args, **kwargs)
        self.timing_util = timing_utility.TimingUtility()
        self.timing_source_config = timing_source_config

    def get(self):
        """Get state of timing source

        Returns:
            dict: 1PPS and frequence timing source state
        """
        # do something to retrieve the state of the feature
        status1 = self.timing_util.get_dpll_status(1)
        status2 = self.timing_util.get_dpll_status(2)
        one_pps_status = {
            "source": src_name_from_dpll1(status1["current"]),
            "priority": [
                {"weighted-priority":value['weighted-priority'],
                "source":src_type}
                for src_type,value in self.timing_source_config["one-pps"].items()
            ],
            "operating-status": status1["status"]["operating_status"],
        }
        frequency_status = {
            "source": src_name_from_dpll2(status2["current"]),
            "priority": [
                {"weighted-priority":value['weighted-priority'],
                "source":src_type}
                for src_type,value in self.timing_source_config["frequency"].items()
            ],
            "operating-status": status2["status"]["operating_status"],
        }
        state = {
            V1_SYSTEM: {
                V1_TIMING: {
                    "timing-status": {
                        "timing-source": {
                            "one-pps-status": one_pps_status,
                            "frequency-status": frequency_status,
                        }
                    }
                }
            }
        }
        return state


if __name__ == "__main__":
    if timing_supported():
        (
            vci.Component("net.vyatta.vci.system.timing")
            .model(
                vci.Model("net.vyatta.vci.system.timing.v1").config(Config()).state(State())
            )
            .run()
            .wait()
        )
