# Copyright (c) 2019 AT&T Intellectual Property.
# Copyright (c) 2018-2019 Nokia.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This source code is part of the near-RT RIC (RAN Intelligent Controller)
# platform project (RICP).
#

"""Structured logging library with Mapped Diagnostic Context

Outputs the log entries to standard out in structured format, json currently.
Severity based filtering.
Supports Mapped Diagnostic Context (MDC).

Set MDC pairs are automatically added to log entries by the library.
"""
from typing import TypeVar
from enum import IntEnum
import sys
import json
import time
import os
import inotify.adapters
import threading


class Level(IntEnum):
    """Severity levels of the log messages."""
    DEBUG = 10
    INFO = 20
    WARNING = 30
    ERROR = 40


LEVEL_STRINGS = {Level.DEBUG: "DEBUG",
                 Level.INFO: "INFO",
                 Level.WARNING: "WARNING",
                 Level.ERROR: "ERROR"}


Value = TypeVar('Value', str, int)


class Logger():
    """Initialize the mdclogging module.
    Calling of the function is optional. If not called, the process name
    (sys.argv[0]) is used by default.

    Keyword arguments:
    name -- name of the component. The name will appear as part of the log
            entries.
    """
    def __init__(self, name: str = sys.argv[0], level: Level = Level.ERROR):
        """Initialize a Logger instance.

            Keyword arguments:
            name -- name of the component. The name will appear as part of the
                    log entries.
        """
        self.procname = name
        self.current_level = level
        self.mdc = {}
        

    # Pass configmap_monitor = True to monitor configmap to change logs dynamically using configmap
    def mdclog_format_init(self,configmap_monitor=False):

        self.mdc = {"PID":"","SYSTEM_NAME":"","HOST_NAME":"","SERVICE_NAME":"","CONTAINER_NAME":"","POD_NAME":""}
        self.get_env_params_values()
        try:
            self.filename = os.environ['CONFIG_MAP_NAME']
            self.dirname = str(self.filename[:self.filename.rindex('/')])
            self.parse_file()

            if configmap_monitor == True:
                self.register_log_change_notify()
            
        except Exception as e:
            print("Unable to Add Watch on ConfigMap File",e)


    def _output_log(self, log: str):
        """Output the log, currently to stdout."""
        print(log)

    def log(self, level: Level, message: str):
        """Log a message.

        Logs the message with the given severity if it is equal or higher than
        the current logging level.

        Keyword arguments:
        level -- severity of the log message
        message -- log message
        """
        if level >= self.current_level:
            log_entry = {}
            log_entry["ts"] = int(round(time.time() * 1000))
            log_entry["crit"] = LEVEL_STRINGS[level]
            log_entry["id"] = self.procname
            log_entry["mdc"] = self.mdc
            log_entry["msg"] = message
            self._output_log(json.dumps(log_entry))

    def error(self, message: str):
        """Log an error message. Equals to log(ERROR, msg)."""
        self.log(Level.ERROR, message)

    def warning(self, message: str):
        """Log a warning message. Equals to log(WARNING, msg)."""
        self.log(Level.WARNING, message)

    def info(self, message: str):
        """Log an info message. Equals to log(INFO, msg)."""
        self.log(Level.INFO, message)

    def debug(self, message: str):
        """Log a debug message. Equals to log(DEBUG, msg)."""
        self.log(Level.DEBUG, message)

    def set_level(self, level: Level):
        """Set current logging level.

        Keyword arguments:
        level -- logging level. Log messages with lower severity will be
                 filtered.
        """
        try:
            self.current_level = Level(level)
        except ValueError:
            pass

    def get_level(self) -> Level:
        """Return the current logging level."""
        return self.current_level

    def add_mdc(self, key: str, value: Value):
        """Add a logger specific MDC.

        If an MDC with the given key exists, it is replaced with the new one.
        An MDC can be removed with remove_mdc() or clean_mdc().

        Keyword arguments:
        key -- MDC key
        value -- MDC value
        """
        self.mdc[key] = value
        

    def get_env_params_values(self):

        try:
            self.mdc['SYSTEM_NAME'] = os.environ['SYSTEM_NAME']
        except:
            self.mdc['SYSTEM_NAME'] = ""

        try:
            self.mdc['HOST_NAME'] = os.environ['HOST_NAME']
        except:
            self.mdc['HOST_NAME'] = ""

        try:
            self.mdc['SERVICE_NAME'] = os.environ['SERVICE_NAME']
        except:
            self.mdc['SERVICE_NAME'] = ""
    
        try:
            self.mdc['CONTAINER_NAME'] = os.environ['CONTAINER_NAME']
        except:
            self.mdc['CONTAINER_NAME'] = ""

        try:
            self.mdc['POD_NAME'] = os.environ['POD_NAME']
        except:
            self.mdc['POD_NAME'] = ""
        try:
            self.mdc['PID'] = os.getpid()
        except:
            self.mdc['PID'] = ""


    
    def update_mdc_log_level_severity(self,level):
        severity_level = Level.ERROR

        if(level == ""):
            print("Invalid Log Level defined in ConfigMap")
        elif((level.upper() == "ERROR") or (level.upper() == "ERR" )):
            severity_level = Level.ERROR
        elif((level.upper() == "WARNING") or (level.upper() == "WARN")):
            severity_level = Level.WARNING
        elif(level.upper() == "INFO"):
            severity_level = Level.INFO
        elif(level.upper() == "DEBUG"):
            severity_level = Level.DEBUG
        
        self.set_level(severity_level)
      
        
        


    def parse_file(self):
        src = open(self.filename,'r')
        level = ""
        for line in src:
            if 'log-level:' in line:
                level_tmp = str(line.split(':')[-1]).strip()
                level = level_tmp
                break
        src.close()
        self.update_mdc_log_level_severity(level)
        



    def monitor_loglevel_change_handler(self):
        i = inotify.adapters.Inotify()
        i.add_watch(self.dirname)
        for event in i.event_gen():
            if (event is not None) and ('IN_MODIFY' in str(event[1]) or 'IN_DELETE' in str(event[1])):
                self.parse_file()


    def register_log_change_notify(self):
        t1 = threading.Thread(target=self.monitor_loglevel_change_handler)
        t1.daemon = True
        try:
            t1.start() 
        except (KeyboardInterrupt, SystemExit):
            cleanup_stop_thread()
            sys.exit()
        



    def get_mdc(self, key: str) -> Value:
        """Return logger's MDC value with the given key or None."""
        try:
            return self.mdc[key]
        except KeyError:
            return None

    def remove_mdc(self, key: str):
        """Remove logger's MDC with the given key."""
        try:
            del self.mdc[key]
        except KeyError:
            pass

    def clean_mdc(self):
        """Remove all MDCs of the logger instance."""
        self.mdc = {}
