'''
    # initialize.py

    - Data 전처리를 목적으로 하는 파일입니다.
'''

import os
import datetime
import csv
import numpy as np


def makeTimeDIR():
    '''
        ### 날짜를 이용한 경로를 생생하는 함수
        - 오늘 날짜를 이용한 경로와 어제 날짜를 이용한 경로를 생성합니다.
        - 생성된 경로는 dictionary 형태로 반환합니다.
    '''
    # 오늘 날짜로 된 경로 생성
    today = datetime.datetime.today()

    tYear = str(today.year)
    tMonth = str(today.month) if today.month >= 10 else '0' + str(today.month)
    tDay = str(today.day) if today.day >= 10 else '0' + str(today.day)

    today_dir = '/' + tYear + '/' + tYear + tMonth + '/' + tYear + tMonth + tDay

    # 오늘을 기준 하루 전 날짜로 된 경로 생성
    yesterday = today - datetime.timedelta(days=1)

    yYear = str(yesterday.year)
    yMonth = str(yesterday.month) if yesterday.month >= 10 else "0" + \
        str(yesterday.month)
    yDay = str(yesterday.day) if yesterday.day >= 10 else "0" + \
        str(yesterday.day)

    yesterday_dir = "/" + yYear + "/" + yYear + yMonth + "/" + yYear + yMonth + yDay

    time_dir = {"today": today_dir, "yesterday": yesterday_dir}
    return time_dir


def loadRawData(link, time_domain, file_name):
    '''
        ### CSV 파일의 내용을 반환하는 함수
        - 제공 받은 링크를 통해 파일을 읽고 반환합니다.
    '''
    raw_data = []
    time_dir = makeTimeDIR()

    file_dir = os.getcwd() + link + time_dir[time_domain] + file_name

    if not os.path.isfile(file_dir):
        print("File doesn't exist on {0}".format(file_dir))
        return None

    data_file = open(file_dir, 'r', newline='')
    csv_data = csv.reader(data_file)

    for line in csv_data:
        raw_data.append(line)

    data_file.close()

    return raw_data


def handleUserRawData(user_data):
    '''
        ### User Raw Data (CSV 파일 데이터) 가공 함수
        - [ 월 / 일 / 시 / 분 / 온도 / 습도 / 광도 ]의 데이터를 변환하는 함수
        - 월 / 일 / 분 제거
        - test_data(분이 제거된 데이터)와 true_data(단위 시간 후 실제 온도)로 나누기
    '''
    user_x = []
    train_t = []

    isFirstLine = True

    for line in user_data:
        _, _, hour, _, temp, humi, lights = line
        user_x.append([int(hour), float(temp), float(humi), float(lights)])
        if isFirstLine:
            isFirstLine = False
        else:
            train_t.append([float(temp)])

    train_t.append(train_t[-1])

    return (user_x, train_t)


def handleOutRawData(out_data):
    '''
        ### Out Raw Data (CSV 파일 데이터) 가공 함수
        - [ 월 / 일 / 시 / 분 / 온도 / 습도 / 기압 / 풍속 ] 데이터를 변환하는 함수
        - '분' 을 제거합니다.
        - 같은 시각의 데이터들은 평균을 구해서 데이터로 저장합니다.
        - 외부 데이터는 Dictionary Data로 최종 반환됩니다.
        - Dictionary의 Key는 '시'가 됩니다.
    '''

    out_dict = {}
    key = None
    counter = 1

    sum_temp, sum_humi, sum_pressure, sum_wind_speed = 0, 0, 0, 0

    for line in out_data:
        month, day, hour, _, temp, humi, pressure, wind_speed = line

        if key == None:
            key = int(hour)
            counter = 1
            sum_temp, sum_humi, sum_pressure, sum_wind_speed = float(
                temp), float(humi), float(pressure), float(wind_speed)

        if key == hour:
            counter += 1
            sum_temp += float(temp)
            sum_humi += float(humi)
            sum_pressure += float(pressure)
            sum_wind_speed += float(wind_speed)
        else:
            out_dict[key] = [int(month), int(day), key, sum_temp/counter, sum_humi /
                             counter, sum_pressure/counter, sum_wind_speed/counter]

            key = int(hour)
            counter = 1
            sum_temp, sum_humi, sum_pressure, sum_wind_speed = float(
                temp), float(humi), float(pressure), float(wind_speed)

    return out_dict


def handleLearningParams(raw_w=None):
    '''
        ### Weights & Bias를 처리하는 함수
        - raw 데이터는 weights와 bias가 합쳐진 상태입니다.
        - raw 데이터를 하나씩 잘라 실수로 변환한 뒤, 마지막의 편향을 잘라냅니다.
    '''

    if raw_w == None:
        return None, None

    weights = []

    for line in raw_w:
        for fig in line:
            weights.append([float(fig)])

    bias = weights.pop()[0]

    return weights, bias


def handleStatsParams(raw_ms=None):
    '''
        ### 평균과 표준편차를 다루는 함수
        - csv 파일로 부터 읽어온 자료를 전체 범주의 평균, 표준편차 그리고 내부 온도에 대한 평균, 표준편차로 나누는 함수 입니다.
    '''
    mean, std = [], []
    raw_mean = raw_ms[0]
    raw_std = raw_ms[1]

    for fig in raw_mean:
        mean.append(float(fig))

    for fig in raw_std:
        std.append(float(fig))

    temp_mean = mean[7]
    temp_std = std[7]

    return mean, std, temp_mean, temp_std


def combineXdata(user_x, out_dict):
    '''
        ### 분리된 입력 데이터를 합치는 함수
        - 사용자 데이터와 외부 데이터를 결합해 입력층의 값으로 가공합니다.
    '''
    train_x = []

    for line in user_x:
        hour, temp, humi, lights = line

        # 데이터 수집이 균일하게 이루어지지 않은 경우 처리
        if hour in out_dict:
            key_hour = hour
        else:
            minimum = 4
            key_hour = None
            for h in range(hour-3, hour + 3):
                if h in out_dict and abs(h - hour) < minimum:
                    minimum = abs(h-hour)
                    key_hour = h

        x = out_dict[key_hour] + [temp, humi, lights]
        train_x.append(x)

    return train_x


def Xnormalize(data):
    '''
        ### 정규화 함수
        - 입력 층의 데이터를 정규화 시킵니다.
        - 월, 일 데이터의 평균과 표준 편차를 계산하여 값을 수정합니다.
    '''

    normalized_data = data.T   # (n,10) -> (10,n)

    mean = np.mean(normalized_data, axis=1)    # 평균 (10, 1)
    std_d = np.std(normalized_data, axis=1)    # 표준편차

    # 월, 일의 평균과 표준편차 지정
    new_mean = []
    for i, fig in enumerate(list(mean)):
        if i == 0:
            new_mean.append(6.5)
        elif i == 1:
            new_mean.append(16.0)
        else:
            new_mean.append(fig)
    new_mean = np.array(new_mean).reshape((-1, 1))

    new_std_d = []
    for i, fig in enumerate(list(std_d)):
        if i == 0:
            new_std_d.append(3.45205253)
        elif i == 1:
            new_std_d.append(8.94427191)
        else:
            new_std_d.append(fig)
    new_std_d = np.array(new_std_d).reshape((-1, 1))

    normalized_data = (normalized_data - new_mean) / new_std_d

    normalized_data = normalized_data.T

    return normalized_data, new_mean, new_std_d


def Tnormalize(data):
    '''
        ### 데이터 정규화 함수
        - 평균과 표준 편차를 이용해 입력된 데이터를 정규화 시킵니다.
        - 현재 입력층의 데이터가 아닌 train 데이터의 참 값을 표준화 시킵니다.
    '''
    n_data = data.T   # (n,1) -> (1,n)

    mean = np.mean(n_data, axis=1)    # 평균
    std_d = np.std(n_data, axis=1)     # 표준편차

    n_data = (n_data - mean) / std_d

    n_data = n_data.T

    return n_data


def preprocessingData(user_link, out_link):
    '''
        # 데이터 분석 전 데이터 전처리 함수
        1. 데이터 로드
        2. 데이터 1차 가공 (handle~RawData)
        3. 데이터 2차 가공 (combineXdata)
        4. 데이터 3차 가공 (nomalize~)
        5. 데이터 넘파이 형식 배열로 변환
        6. 반환
    '''
    raw_user_data = loadRawData(user_link, "yesterday", "/weather.csv")
    raw_out_data = loadRawData(out_link, "yesterday", "/weather.csv")
    raw_parameters = loadRawData(
        user_link, "yesterday", "/analysis_parameters.csv")

    user_x, train_t = handleUserRawData(raw_user_data)
    out_dict = handleOutRawData(raw_out_data)
    weights, bias = handleLearningParams(raw_parameters)

    train_x = combineXdata(user_x, out_dict)

    train_x = np.array(train_x)  # (n ,10)
    train_x, mean, std_d = Xnormalize(train_x)

    train_t = np.array(train_t)  # (10,1)
    train_t = Tnormalize(train_t)

    weights = np.array(weights) if weights != None else None
    bias = float(bias) if bias != None else None

    return train_x, train_t, weights, bias, mean, std_d
