# -*- coding: utf-8 -*-
from datetime import timedelta, datetime
from dateutil.relativedelta import relativedelta
from time import time
import logging
import yaml

class Model:
    def __init__(self, current_time, timestep_length, config_path, n_timesteps=None, args=None):
        self._current_time = current_time
        self.timestep_length = timestep_length
        self.n_timesteps = n_timesteps
        self.config = self.setup_config(config_path)
        self.args = args
        self.logger = self.create_logger()
        self.logger.info("Initializing model")
        self.current_timestep = 0
        self.running = True

    @property
    def bounds(self):
        """Property returning the bounds of the current model as tuple

        Returns:
            tuple: xmin, xmax, ymin, ymax bounds of the model

        """
        return self.xmin, self.xmax, self.ymin, self.ymax

    @property
    def xmin(self):
        """Get model xmin

        Returns:
            float: xmin
        """
        return self.area.geoms['xmin']

    @property
    def xmax(self):
        """Get model xmax

        Returns:
            float: xmax
        """
        return self.area.geoms['xmax']

    @property
    def ymin(self):
        """Get model ymin

        Returns:
            float: ymin
        """
        return self.area.geoms['ymin']

    @property
    def ymax(self):
        """Get model ymax

        Returns:
            float: ymax
        """
        return self.area.geoms['ymax']

    @property
    def current_time_fmt(self):
        if self.timestep_length.days >= 1:
            dateformat = '%d %b %Y'
        else:
            dateformat = '%d %b %Y %H:%M'
        formatted_date = self.current_time.strftime(dateformat)
        if formatted_date.startswith('0'):  # Windows cannot handle %-d, so this is a safe way to remove the preceding 0
            formatted_date = formatted_date[1:]
        return formatted_date

    @property
    def current_time(self):
        """
        Returns:
            datetime.datetime: current model time
        """
        return self._current_time

    @current_time.setter
    def current_time(self, v):
        self._current_time = v

    @property
    def current_timestep(self):
        """
        Returns:
            int: current model timestep
        """
        return self._current_timestep

    @current_timestep.setter
    def current_timestep(self, v):
        self._current_timestep = v

    def create_logger(self):
        logger = logging.getLogger('honeybees')
        
        if self.config and 'logging' in self.config and 'loglevel' in self.config['logging']:
            loglevel = self.config['logging']['loglevel']
        else:
            loglevel = 'INFO'
        logger.setLevel(logging.getLevelName(loglevel))

        if self.config and 'logging' in self.config and 'logfile' in self.config['logging']:
            logfile = self.config['logging']['logfile']
        else:
            logfile = 'GEB.log'
        file_handler = logging.FileHandler(logfile, mode='w')
        logger.addHandler(file_handler)

        formatter = logging.Formatter('%(asctime)s : %(levelname)s : %(message)s')
        file_handler.setFormatter(formatter)

        return logger

    def setup_config(self, config_path):
        if config_path is not None:
            with open(config_path, 'r') as f:
                config = yaml.load(f, Loader=yaml.FullLoader)
            return config

    def parse_step_str(self, step_string):
        if step_string == 'day':
            new_time = self.current_time + timedelta(days=1)
            difference = new_time - self.current_time
            n = int(difference / self.timestep_length)
        elif step_string == 'week':
            new_time = self.current_time + timedelta(days=7)
            difference = new_time - self.current_time
            n = int(difference / self.timestep_length)
        elif step_string == 'month':
            new_time = self.current_time + relativedelta(months=1)
            difference = new_time - self.current_time
            n = int(difference / self.timestep_length)
        elif step_string == 'year':
            new_time = self.current_time + relativedelta(years=1)
            if isinstance(self.timestep_length, relativedelta):
                assert self.timestep_length.years == 1
                n = 1
            else:
                difference = new_time - self.current_time
                n = int(difference / self.timestep_length)
        elif step_string == 'decade':
            new_time = self.current_time + relativedelta(years=10)
            if isinstance(self.timestep_length, relativedelta):
                assert self.timestep_length.years == 1
                n = 10
            else:
                difference = new_time - self.current_time
                n = int(difference / self.timestep_length)
        elif step_string == 'century':
            new_time = self.current_time + relativedelta(years=100)
            if isinstance(self.timestep_length, relativedelta):
                assert self.timestep_length.years == 1
                n = 100
            else:
                difference = new_time - self.current_time
                n = int(difference / self.timestep_length)
        else:
            raise ValueError(f"{step_string} not a known step_size")
        return n
        
    def step(self, step_size=1, report=True):

        if isinstance(step_size, str):
            n = self.parse_step_str(step_size)
        else:
            n = step_size
        
        assert isinstance(n, int) and n > 0
        for _ in range(n):
            t0 = time()
            # print('Simulating agent behavior')
            self.agents.step()
            t1 = time()
            if report:
                self.reporter.step()
            t2 = time()
            # print('\tstep time', t1- t0)
            # print('\treport time', t2 - t1)
            self.current_time += self.timestep_length
            self.current_timestep += 1

    def run(self, report=True):
        for _ in range(self.n_timesteps):
            self.step(report=report)
        self.report()

    def report(self):
        return self.reporter.report()