# -*- coding: utf-8 -*-
"""
Created on Wed Sep 19 13:06:35 2018

@author: hrajiv
"""
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.layers import Embedding
from keras.layers import LSTM
from keras import optimizers
import pandas as pd
import sys
import numpy as np
import xgboost
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.metrics import explained_variance_score
from eli5.permutation_importance import get_score_importances
from sklearn.model_selection import GridSearchCV
import traceback

name='avarana'

def get_function_name():
    return traceback.extract_stack(None, 2)[0][2]

from tensorflow import set_random_seed
set_random_seed(7)

from tensorflow import set_random_seed
set_random_seed(2)

def parser(x):
	return pd.datetime.strptime(x, '%Y%m%d')

def lag(array, start_index, end_index, lag):
    lag_start_index = start_index-lag
    lag_end_index = end_index-lag
    yyy = array[lag_start_index:lag_end_index]
    return yyy

def mean_absolute_percentage_error(y_true, y_pred): 
    y_true[y_true==0] = y_true[y_true>0].min() 
    h1 = np.abs(y_true - y_pred)
    h2 = np.divide(h1, y_true)
    return np.mean(h2)

def tf_mape(y_true, y_pred):
    y_true[y_true==0] = y_true[y_true>0].min() 
    h1 = np.abs(y_true - y_pred)
    h2 = np.divide(h1, y_true)
    return np.mean(h2)

def weighted_absolute_percentage_error(y_true, y_pred): 
    return np.abs(y_true - y_pred).sum()/y_true.sum()

def nnet_regressor(X, y, X_test=[], y_test=[], hidden_layers=[],
                   activation='relu', loss='mape', verbose=0):
    if type(hidden_layers) is not list:
        print("Hidden layers must be a list")
        return None
        
    model = Sequential()
    
    if len(hidden_layers) > 0 and np.sum(hidden_layers) > 0:
        first_layer = 0
        for layer_size in hidden_layers:
            if layer_size > 0 and first_layer == 0:
                model.add(Dense(layer_size, input_dim=X.shape[1], activation='linear'))
                first_layer = 1
            elif layer_size > 0:
                model.add(Dense(layer_size, activation='relu'))
        model.add(Dense(1, activation='linear')) #hard coded to 1 output layer
    else:
        model.add(Dense(1, input_dim=X.shape[1], activation='linear')) #hard coded to 1 output layer

    opt_fun = 'rmsprop'
    opt_fun = optimizers.Nadam(lr=0.002) 
    model.compile(loss=loss, optimizer=opt_fun)

    def scorer(X, y):
        y_pred = model.predict(X)
        return 1-mean_absolute_percentage_error(y, y_pred)    
    
#    gsc = GridSearchCV(
#        estimator=model,
#        param_grid={
#            #'neurons': range(18,31,4),
#            'noise': [x/20.0 for x in range(3, 7)],
#        },
#        scoring='neg_absolute_error',
#        cv=5
#    )   
    
    
    
#    last_min_mape = 100
#    last_min_layer = []
#    
#    hidden_layers = []
#    for h in range(0,X.shape[1]):
#        hidden_layers.append(0)
#        for i in range(0,len(X)+1): 
#            hidden_layers[h] = i
#            model, predictions, mape = manthan_nnet(X,Y,X_test,test,hidden_layers)
#            if mape < last_min_mape:
#                last_min_layer = hidden_layers
#                last_min_mape = mape
#    
#    print('Min layer size = ',last_min_layer, ' last min MAPE= ', last_min_mape)
    
    model.fit(X.values, y.values, batch_size=round(len(y)/2), epochs=50, verbose=verbose)
       
#   Set defaults if there is no test period in the inputs
    predictions = []
    mape = 0
    
    if len(X_test) > 0:
       if verbose > 0:
           base_score, score_decreases = get_score_importances(scorer, X_test.values, y_test.values)
           feature_importances = np.mean(score_decreases, axis=0)
           abs_feature_importances = np.abs(feature_importances )
           feature_importances_table = pd.DataFrame(np.transpose([X.columns, feature_importances, abs_feature_importances]), 
                                                    columns=['Feature','Score', 'Abs Score'])
           print(feature_importances_table.sort_values('Abs Score', ascending=False)) 
        
       predictions = model.predict(X_test).ravel()
       error = mean_squared_error(y_test.values, predictions)
       mape = mean_absolute_percentage_error(y_test.values, predictions)
       wape = weighted_absolute_percentage_error(y_test.values, predictions)
       r2 = r2_score(y_test.values, predictions)
       if verbose > 0:
           print('Hidden layers: ',hidden_layers,' R2: %.3f Test MSE: %.3f ~MAPE: %.6f WAPE: %.3f' % (r2, error, mape, wape))    
    
    return model, predictions, mape;

# define base model
def baseline_model():
	# create model
	model = Sequential()
	model.add(Dense(5, input_dim=5, kernel_initializer='normal', activation='relu'))
	model.add(Dense(1, kernel_initializer='normal'))
	# Compile model
	model.compile(loss='mean_squared_error', optimizer='adam')
	return model

# XGB Configuration
def xgb_regressor(X, Y, X_test, Y_test):
    xgb = xgboost.XGBRegressor(n_estimators=100, 
                           learning_rate=0.1, 
                           gamma=0.1, 
                           subsample=0.8,
                           colsample_bytree=0.75, 
                           max_depth=3,
                           min_child_weight=1)

    xgb.fit(X.values,Y.values)
    
    param_test1 = {
     'max_depth':range(3,100,2),
     'min_child_weight':range(1,6,2)
#     'learning_rate':np.arange(0.1,1,0.1),
#     'gamma':[i/10.0 for i in range(0,5)],
#     'subsample':[i/10.0 for i in range(6,10)],
#     'colsample_bytree':[i/10.0 for i in range(6,10)]
     }

    gsearch1 = GridSearchCV(estimator = xgb, 
                        param_grid = param_test1, 
                        n_jobs=1,iid=False, 
                        cv=5)

    gsearch1.fit(X.values,Y.values)

    print('Grid Score')
    print(gsearch1.grid_scores_)
    print('Best params')
    print(gsearch1.best_params_)
    print('Best Score')
    print(gsearch1.best_score_)
    
    predictions = xgb.predict(X_test.values)
    mape = mean_absolute_percentage_error(Y_test.values, predictions)
    evs = explained_variance_score(predictions, Y_test.values)
    return xgb, predictions, mape, evs

def lstm_regressor(X, Y, X_test, Y_test):
    max_features = 1024
    
    model = Sequential()
    model.add(Embedding(max_features, output_dim=256))
    model.add(LSTM(128))
    model.add(Dropout(0.5))
    model.add(Dense(1, activation='sigmoid'))
    
    model.compile(loss='mape',
                  optimizer='rmsprop',
                  metrics=['accuracy'])
    
    model.fit(x_train, y_train, batch_size=16, epochs=10)
    score = model.evaluate(x_test, y_test, batch_size=16)
