from pathlib import Path
from collections import namedtuple
import random
import math

import numpy as np
import pandas as pd
import sqlalchemy
import matplotlib.pyplot as plt
import pytz
%matplotlib inline

import people
from people import Activity, WeekMarkovChain

PATH_TO_DB = Path('../target/notebook/test-scenario.db').absolute()
PATH_TO_DB.parent.mkdir(parents=True, exist_ok=True)
MARKOV_CHAIN_INDEX_TABLE_NAME = 'markovChains'
DWELLINGS_TABLE_NAME = 'dwellings'
PEOPLE_TABLE_NAME = 'people'
ENVIRONMENT_TABLE_NAME = 'environment'
PARAMETERS_TABLE_NAME = 'parameters'

random.seed('test-scenario-creation')

def df_to_db(df, table_name):
    disk_engine = sqlalchemy.create_engine('sqlite:///{}'.format(PATH_TO_DB))
    df.to_sql(name=table_name, con=disk_engine)

"""
Explanation: Test Scenario Creation
This notebook creates a test scenario for the energy agents simulation. The scenario file contains all necessary information on dwellings and on citizens and simulation parameter. Technically, the scenario file is a SQLite database.
The database has the following tables:

markovChains : a simple mapping from markov chain id to markov chain table name
markov_chain001 .. markov_chainXYZ: one table per heterogeneous markov chain. Name is arbitrary.
dwellings: a table for all dwellings, containing all thermal parameters and a link to UKBuildings
people: a table for all people, a simple mapping to a dwelling and a link to the markov chain
environment: a table with all environmental time series, currently temperature only
parameters: a table with all simulation parameters
End of explanation
"""


from datetime import time, timedelta

def weekday_time_series1():
    index = [time(0, 0), time(12, 0)]
    values1 = [Activity.HOME, Activity.NOT_AT_HOME]
    values2 = [Activity.HOME, Activity.HOME]
    values3 = [Activity.NOT_AT_HOME, Activity.NOT_AT_HOME]
    return pd.DataFrame(
        index=index,
        data={'person1': values1, 'person2': values2, 'person3': values3}
    )

def weekday_time_series2():
    index = [time(0, 0), time(12, 0)]
    values1 = [Activity.HOME, Activity.HOME]
    values2 = [Activity.HOME, Activity.HOME]
    values3 = [Activity.NOT_AT_HOME, Activity.NOT_AT_HOME]
    values4 = [Activity.HOME, Activity.NOT_AT_HOME]
    return pd.DataFrame(
        index=index,
        data={'person1': values1, 'person2': values2, 'person3': values3, 'person4': values4}
    )


def weekend_day_time_series1():
    index = [time(0, 0), time(12, 0)]
    values1 = [Activity.HOME, Activity.HOME]
    values2 = [Activity.NOT_AT_HOME, Activity.HOME]
    values3 = [Activity.HOME, Activity.NOT_AT_HOME]
    return pd.DataFrame(
        index=index,
        data={'person1': values1, 'person2': values2, 'person3': values3}
    )

def weekend_day_time_series2():
    index = [time(0, 0), time(12, 0)]
    values1 = [Activity.HOME, Activity.NOT_AT_HOME]
    values2 = [Activity.HOME, Activity.HOME]
    values3 = [Activity.NOT_AT_HOME, Activity.NOT_AT_HOME]
    values4 = [Activity.HOME, Activity.NOT_AT_HOME]
    return pd.DataFrame(
        index=index,
        data={'person1': values1, 'person2': values2, 'person3': values3, 'person4': values4}
    )

def markov_chain(weekday_time_series, weekend_day_time_series):
    return WeekMarkovChain(
        weekday_time_series=weekday_time_series,
        weekend_time_series=weekend_day_time_series,
        time_step_size=timedelta(hours=12)
    )


def full_chain_to_sql(full_chain, table_name):
    df = full_chain.to_dataframe()
    df.fromActivity = [str(x) for x in df.fromActivity]
    df.toActivity = [str(x) for x in df.toActivity]
    df_to_db(df, table_name)
    

def index_table_to_sql(chain_index):
    df = pd.Series(chain_index, name='tablename')
    df_to_db(df, MARKOV_CHAIN_INDEX_TABLE_NAME)

full_chain_to_sql(markov_chain(weekday_time_series1(), weekend_day_time_series1()), 'markov_chain001')
full_chain_to_sql(markov_chain(weekday_time_series2(), weekend_day_time_series2()), 'markov_chain002')
index_table_to_sql({89: 'markov_chain001', 22: 'markov_chain002'})

"""
Explanation: Activity Markov Chains
In the following arbitrary test markov chains are created.
End of explanation
"""


class UniformDistributedParameter():
    
    def __init__(self, expected_value, variation_in_percent):
        self.__expected_value = expected_value
        self.__random_max = expected_value * variation_in_percent / 100
        
    def sample(self):
        return self.__expected_value + random.uniform(-self.__random_max, self.__random_max)
   

CONDITIONED_FLOOR_AREA = 100 # m^2
THERMAL_MASS_CAPACITY = UniformDistributedParameter(165000 * CONDITIONED_FLOOR_AREA, 20.0)
THERMAL_MASS_AREA = UniformDistributedParameter(2.5 * CONDITIONED_FLOOR_AREA, 20.0)
ROOM_HEIGHT = UniformDistributedParameter(3, 20.0)
WINDOW_TO_WALL_RATIO = UniformDistributedParameter(0.19, 20.0)
U_VALUE_WALL = UniformDistributedParameter(0.26, 20.0)
U_VALUE_ROOF = UniformDistributedParameter(0.12, 20.0)
U_VALUE_FLOOR = UniformDistributedParameter(0.40, 20.0)
U_VALUE_WINDOW = UniformDistributedParameter(1.95, 20.0)
TRANSMISSION_ADJUSTMENT_GROUND = UniformDistributedParameter(0.91, 20.0)
NATURAL_VENTILATION_RATE = UniformDistributedParameter(0.65, 20.0)
MAX_HEATING_POWER = 0
INITIAL_TEMPERATURE = UniformDistributedParameter(22, 35.2)
NUMBER_DWELLINGS = 100

from itertools import chain

def create_dwellings():
    ids = list(range(104, NUMBER_DWELLINGS + 104))
    return pd.DataFrame(
        index=ids,
        data = {
            'thermalMassCapacity': [THERMAL_MASS_CAPACITY.sample() for unused in ids],
            'thermalMassArea': [THERMAL_MASS_AREA.sample() for unused in ids],
            'floorArea': CONDITIONED_FLOOR_AREA,
            'roomHeight': [ROOM_HEIGHT.sample() for unused in ids],
            'windowToWallRatio': [WINDOW_TO_WALL_RATIO.sample() for unused in ids],
            'uWall': [U_VALUE_WALL.sample() for unused in ids],
            'uRoof': [U_VALUE_ROOF.sample() for unused in ids],
            'uFloor': [U_VALUE_FLOOR.sample() for unused in ids],
            'uWindow': [U_VALUE_WINDOW.sample() for unused in ids],
            'transmissionAdjustmentGround': [TRANSMISSION_ADJUSTMENT_GROUND.sample() for unused in ids],
            'naturalVentilationRate': [NATURAL_VENTILATION_RATE.sample() for unused in ids],
            'maxHeatingPower': MAX_HEATING_POWER,
            'initialTemperature': [INITIAL_TEMPERATURE.sample() for unused in ids],
            'heatingControlStrategy': [random.choice(['OFF', 'FLAT', 'TIME_TRIGGERED', 'PRESENCE_TRIGGERED']) 
                                       for unused in ids],
            'districtId': list(chain(*[[x]*10 for x in range(NUMBER_DWELLINGS // 10)]))
        }
    )

dwellings = create_dwellings()
dwellings.head()

assert len(dwellings.index) == NUMBER_DWELLINGS

df_to_db(dwellings, DWELLINGS_TABLE_NAME)

"""
Explanation: Dwellings
End of explanation
"""


NUMBER_PEOPLE = 200

def create_people():
    ids = list(range(1, NUMBER_PEOPLE * 2, 2))
    return pd.DataFrame(
        index=ids,
        data = {
            'markovChainId': [{1: 89, 2:22}[round(random.uniform(1, 2))] for unused in ids],
            'dwellingId': [round(random.uniform(104, NUMBER_DWELLINGS + 103)) for unused in ids],
            'initialActivity': [{1: 'HOME', 2: 'NOT_AT_HOME'}[round(random.uniform(1, 2))] for unused in ids],
            'randomSeed': [123456789 + person_id for person_id in ids],
            'activeMetabolicRate': 100.0,
            'passiveMetabolicRate': 50.0
        }
    )

people = create_people()
people.head()

assert len(people.index) == NUMBER_PEOPLE
assert all(dwellingId in dwellings.index for dwellingId in people.dwellingId)

df_to_db(people, PEOPLE_TABLE_NAME)

"""
Explanation: People
End of explanation
"""


def create_temperature_profile():
    index = pd.date_range('2015-01-01 00:00', '2015-12-31 12:00', freq='12H')
    x = np.linspace(0, len(index) - 1, num=len(index))
    return pd.Series(
        name='temperature',
        index=index,
        data=10 + 5 * np.sin(-1 + x / 2 * 2 * math.pi) + 10 * np.sin(-1.5 + x / len(index) * 2 * math.pi)
    )
    
def plot_temperature(df):
    fig = plt.figure(figsize=(14, 5))
    _ = plt.plot(df)
    plt.title("Synthesized temperature profile")
    plt.ylabel("temperature [˚C]")
    plt.xlabel("datetime")

temperature = create_temperature_profile()
plot_temperature(temperature)

df_to_db(temperature, ENVIRONMENT_TABLE_NAME)

"""
Explanation: Environment
End of explanation
"""


def create_simulation_parameters():
    return pd.DataFrame(
        index = [1],
        data = {
            'initialDatetime': temperature.index[0],
            'timeStepSize_in_min': timedelta(hours=12).total_seconds() / 60, # timedelta not supported in SQL
            'numberTimeSteps': 90,
            'logThermalPower': False,
            'logTemperature': True,
            'logActivity': True,
            'logAggregated': False,
            'setPointWhileHome': 24.0,
            'setPointWhileAsleep': 18.0,
            'wakeUpTime': time(8, 0),
            'leaveHomeTime': time(9, 0),
            'comeHomeTime': time(18, 0),
            'bedTime': time(22, 0)
        }
    )

simulation_parameters = create_simulation_parameters()
simulation_parameters.head()

df_to_db(simulation_parameters, PARAMETERS_TABLE_NAME)

"""
Explanation: Simulation Parameters
End of explanation
"""


