## ------------------------------------------------------------------------------------------------------------------ ##
## ------------------------------------------------------------------------------------------------------------------ ##
##                         ___   ___  ________    ________         ______    ________ ________                        ##
##                        |  |  |  | |   ____|   /       |        /  __  \  |   ____||   ____|                        ##
##                        |  |__|  | |  |__     |   (----` ______|  |  |  | |  |__   |  |__                           ##
##                        |   __   | |   __|     \   \    |______|  |  |  | |   __|  |   __|                          ##
##                        |  |  |  | |  |____.----)   |          |  `--'  | |  |     |  |                             ##
##                        |__|  |__| |_______|_______/            \______/  |__|     |__|                             ##
##                                                                                                                    ##
## ------------------------------------------------------------------------------------------------------------------ ##
## ------------------------------------------------------------------------------------------------------------------ ##

# Import packages
import re
import sys
import pdb
import copy
import time
import functools
import numpy as np
import importlib_resources


class TimerError(Exception):
    """A custom exception used to report errors in use of Timer class"""

class Timer:
    def __init__(self, name=None):
        self._start_time = None
        self.name = name

    def start(self):
        """Start a new timer"""
        if self._start_time is not None:
            raise TimerError(f"Timer is running. Use .stop() to stop it")

        self._start_time = time.perf_counter()

    def stop(self):
        """Stop the timer, and report the elapsed time"""
        if self._start_time is None:
            raise TimerError(f"Timer is not running. Use .start() to start it")

        elapsed_time = time.perf_counter() - self._start_time
        self._start_time = None
        if self.name:
            print("Elapsed time for", self.name, "is: {:0.5e} seconds".format(elapsed_time))
        else:
            print("Elapsed time is: {:0.5e} seconds".format(elapsed_time))

    def __enter__(self):
        """Start a new timer as a context manager"""
        self.start()
        return self

    def __exit__(self, *exc_info):
        """Stop the context manager timer"""
        self.stop()

    def __call__(self, func):
        """Support using Timer as a decorator"""
        @functools.wraps(func)
        def wrapper_timer(*args, **kwargs):
            with self:
                return func(*args, **kwargs)
        return wrapper_timer


def read_configuration_file(filename):
    """ Read the configuration file that stores the input data """
    try:
        IN = {}
        with open(filename, 'r') as reader:
            for line in reader:
                line  = re.sub(" ", "", line)                               # Eliminate white spaces
                line  = re.sub("[ \t\n\"()\[\]{}]", "", line)               # Eliminate " [ ] ( ) { } \n characters
                words = re.split("[=,;#%]", line)                           # Split over = , ; # % characters
                if not any([words[0] in symbol for symbol in ['%','#']]):   # Ignore lines starting by % or #
                    words = list(filter(None, words))                       # Variables with no value are empty lists
                    for i in range(len(words)):
                        try:
                            words[i] = float(words[i])                      # Try to convert to float when possible
                        except:
                            words[i] = words[i]                             # Handle as a string otherwise
                    if len(words[1:]) == 1:
                        IN[words[0]] = words[1]                             # Define variable as a float or string
                    else:
                        IN[words[0]] = words[1:]                            # Define variable as a list

            # Add configuration file path to the dictionary
            IN['CONFIGURATION_PATH'] = filename
    except:
        raise Exception('\n\n\n The configuration file could not be read. Exiting the program.')

    return IN


def write_configuration_file(filename, IN):
    """ Write a configuration file from dictionary """
    with open(filename, 'w') as writer:
        for key, value in IN.items():

            if "POWER" in key or "HEAT" in key:
                if isinstance(value, list):
                    value = [item / 1e6 for item in value]
                else:
                    value = value / 1e6

            if not isinstance(value, list):
                if not isfloat(value):
                    value = '"' + str(value) + '"'
            writer.write("%s = %s\n" % (key, value))


def print_dictionary(IN):
    """ Print a configuration dictionary with a readable format """
    for key, value in IN.items():
        print("{:<24}{}".format(key, value))


def isfloat(value):
  try:
    float(value)
    return True
  except ValueError:
    return False


def print_progress(iteration, total, prefix='', suffix='', decimals=2, barLength=70):

    """ Call in a loop to create terminal progress bar

    @params:
        iteration   - Required  : current iteration (Int)
        total       - Required  : total iterations (Int)
        prefix      - Optional  : prefix string (Str)
        suffix      - Optional  : suffix string (Str)

    """

    filledLength    = int(round(barLength * iteration / (total-1)))
    percents        = round(100.00 * (iteration / (total-1)), decimals)
    if percents>100.0: percents=100.0
    bar             = '#' * filledLength + '-' * (barLength - filledLength)
    sys.stdout.write('\r%s [%s] %s%s %s' % (prefix, bar, percents, '%', suffix)),
    sys.stdout.flush()
    if iteration == total - 1:
        print("\n")

def get_defaults():
    filename = importlib_resources.files("hes_off.data_files").joinpath("default.cfg")
    return read_configuration_file(filename)

def print_banner():
    """ Print the HES-OFF banner"""
    print("## ---------------------------------------------------------------------------------- ##")
    print("## ---------------------------------------------------------------------------------- ##")
    print("##         ___   ___  ________    ________         ______    ________ ________        ##")
    print("##        |  |  |  | |   ____|   /       |        /  __  \  |   ____||   ____|        ##")
    print("##        |  |__|  | |  |__     |   (----` ______|  |  |  | |  |__   |  |__           ##")
    print("##        |   __   | |   __|     \   \    |______|  |  |  | |   __|  |   __|          ##")
    print("##        |  |  |  | |  |____.----)   |          |  `--'  | |  |     |  |             ##")
    print("##        |__|  |__| |_______|_______/            \______/  |__|     |__|             ##")
    print("##                                                                                    ##")
    print("## ---------------------------------------------------------------------------------- ##")
    print("## ---------------------------------------------------------------------------------- ##")
    print('\n')







## ------------------------------------------------------------------------------------------------------------------ ##
## ------------------------------------------------------------------------------------------------------------------ ##
##                         ___   ___  ________    ________         ______    ________ ________                        ##
##                        |  |  |  | |   ____|   /       |        /  __  \  |   ____||   ____|                        ##
##                        |  |__|  | |  |__     |   (----` ______|  |  |  | |  |__   |  |__                           ##
##                        |   __   | |   __|     \   \    |______|  |  |  | |   __|  |   __|                          ##
##                        |  |  |  | |  |____.----)   |          |  `--'  | |  |     |  |                             ##
##                        |__|  |__| |_______|_______/            \______/  |__|     |__|                             ##
##                                                                                                                    ##
## ------------------------------------------------------------------------------------------------------------------ ##
## ------------------------------------------------------------------------------------------------------------------ ##

# Import packages
import re
import sys
import pdb
import copy
import time
import functools
import numpy as np
import importlib_resources


class TimerError(Exception):
    """A custom exception used to report errors in use of Timer class"""

class Timer:
    def __init__(self, name=None):
        self._start_time = None
        self.name = name

    def start(self):
        """Start a new timer"""
        if self._start_time is not None:
            raise TimerError(f"Timer is running. Use .stop() to stop it")

        self._start_time = time.perf_counter()

    def stop(self):
        """Stop the timer, and report the elapsed time"""
        if self._start_time is None:
            raise TimerError(f"Timer is not running. Use .start() to start it")

        elapsed_time = time.perf_counter() - self._start_time
        self._start_time = None
        if self.name:
            print("Elapsed time for", self.name, "is: {:0.5e} seconds".format(elapsed_time))
        else:
            print("Elapsed time is: {:0.5e} seconds".format(elapsed_time))

    def __enter__(self):
        """Start a new timer as a context manager"""
        self.start()
        return self

    def __exit__(self, *exc_info):
        """Stop the context manager timer"""
        self.stop()

    def __call__(self, func):
        """Support using Timer as a decorator"""
        @functools.wraps(func)
        def wrapper_timer(*args, **kwargs):
            with self:
                return func(*args, **kwargs)
        return wrapper_timer


def read_configuration_file(filename):
    """ Read the configuration file that stores the input data """
    try:
        IN = {}
        with open(filename, 'r') as reader:
            for line in reader:
                line  = re.sub(" ", "", line)                               # Eliminate white spaces
                line  = re.sub("[ \t\n\"()\[\]{}]", "", line)               # Eliminate " [ ] ( ) { } \n characters
                words = re.split("[=,;#%]", line)                           # Split over = , ; # % characters
                if not any([words[0] in symbol for symbol in ['%','#']]):   # Ignore lines starting by % or #
                    words = list(filter(None, words))                       # Variables with no value are empty lists
                    for i in range(len(words)):
                        try:
                            words[i] = float(words[i])                      # Try to convert to float when possible
                        except:
                            words[i] = words[i]                             # Handle as a string otherwise
                    if len(words[1:]) == 1:
                        IN[words[0]] = words[1]                             # Define variable as a float or string
                    else:
                        IN[words[0]] = words[1:]                            # Define variable as a list

            # Add configuration file path to the dictionary
            IN['CONFIGURATION_PATH'] = filename
    except:
        raise Exception('\n\n\n The configuration file could not be read. Exiting the program.')

    return IN


def write_configuration_file(filename, IN):
    """ Write a configuration file from dictionary """
    with open(filename, 'w') as writer:
        for key, value in IN.items():

            if "POWER" in key or "HEAT" in key:
                if isinstance(value, list):
                    value = [item / 1e6 for item in value]
                else:
                    value = value / 1e6

            if not isinstance(value, list):
                if not isfloat(value):
                    value = '"' + str(value) + '"'
            writer.write("%s = %s\n" % (key, value))


def print_dictionary(IN):
    """ Print a configuration dictionary with a readable format """
    for key, value in IN.items():
        print("{:<24}{}".format(key, value))


def isfloat(value):
  try:
    float(value)
    return True
  except ValueError:
    return False


def print_progress(iteration, total, prefix='', suffix='', decimals=2, barLength=70):

    """ Call in a loop to create terminal progress bar

    @params:
        iteration   - Required  : current iteration (Int)
        total       - Required  : total iterations (Int)
        prefix      - Optional  : prefix string (Str)
        suffix      - Optional  : suffix string (Str)

    """

    filledLength    = int(round(barLength * iteration / (total-1)))
    percents        = round(100.00 * (iteration / (total-1)), decimals)
    if percents>100.0: percents=100.0
    bar             = '#' * filledLength + '-' * (barLength - filledLength)
    sys.stdout.write('\r%s [%s] %s%s %s' % (prefix, bar, percents, '%', suffix)),
    sys.stdout.flush()
    if iteration == total - 1:
        print("\n")

def get_defaults():
    filename = importlib_resources.files("hes_off.data_files").joinpath("default.cfg")
    return read_configuration_file(filename)

def print_banner():
    """ Print the HES-OFF banner"""
    print("## ---------------------------------------------------------------------------------- ##")
    print("## ---------------------------------------------------------------------------------- ##")
    print("##         ___   ___  ________    ________         ______    ________ ________        ##")
    print("##        |  |  |  | |   ____|   /       |        /  __  \  |   ____||   ____|        ##")
    print("##        |  |__|  | |  |__     |   (----` ______|  |  |  | |  |__   |  |__           ##")
    print("##        |   __   | |   __|     \   \    |______|  |  |  | |   __|  |   __|          ##")
    print("##        |  |  |  | |  |____.----)   |          |  `--'  | |  |     |  |             ##")
    print("##        |__|  |__| |_______|_______/            \______/  |__|     |__|             ##")
    print("##                                                                                    ##")
    print("## ---------------------------------------------------------------------------------- ##")
    print("## ---------------------------------------------------------------------------------- ##")
    print('\n')







## ------------------------------------------------------------------------------------------------------------------ ## ## ------------------------------------------------------------------------------------------------------------------ ## ##                         ___   ___  ________    ________         ______    ________ ________                        ## ##                        |  |  |  | |   ____|   /       |        /  __  \  |   ____||   ____|                        ## ##                        |  |__|  | |  |__     |   (----` ______|  |  |  | |  |__   |  |__                           ## ##                        |   __   | |   __|     \   \    |______|  |  |  | |   __|  |   __|                          ## ##                        |  |  |  | |  |____.----)   |          |  `--'  | |  |     |  |                             ## ##                        |__|  |__| |_______|_______/            \______/  |__|     |__|                             ## ##                                                                                                                    ## ## ------------------------------------------------------------------------------------------------------------------ ## ## ------------------------------------------------------------------------------------------------------------------ ## # Import packages A custom exception used to report errors in use of Timer class Start a new timer Stop the timer, and report the elapsed time Start a new timer as a context manager Stop the context manager timer Support using Timer as a decorator Read the configuration file that stores the input data # Eliminate white spaces # Eliminate " [ ] ( ) { } \n characters #%]", line)                           # Split over = , ; # % characters # Ignore lines starting by % or # # Variables with no value are empty lists # Try to convert to float when possible # Handle as a string otherwise # Define variable as a float or string # Define variable as a list # Add configuration file path to the dictionary Write a configuration file from dictionary Print a configuration dictionary with a readable format Call in a loop to create terminal progress bar      @params:         iteration   - Required  : current iteration (Int)         total       - Required  : total iterations (Int)         prefix      - Optional  : prefix string (Str)         suffix      - Optional  : suffix string (Str) Print the HES-OFF banner # ---------------------------------------------------------------------------------- ##") # ---------------------------------------------------------------------------------- ##") #         ___   ___  ________    ________         ______    ________ ________        ##") #        |  |  |  | |   ____|   /       |        /  __  \  |   ____||   ____|        ##") #        |  |__|  | |  |__     |   (----` ______|  |  |  | |  |__   |  |__           ##") #        |   __   | |   __|     \   \    |______|  |  |  | |   __|  |   __|          ##") #        |  |  |  | |  |____.----)   |          |  `--'  | |  |     |  |             ##") #        |__|  |__| |_______|_______/            \______/  |__|     |__|             ##") #                                                                                    ##") # ---------------------------------------------------------------------------------- ##") # ---------------------------------------------------------------------------------- ##")