import random
import itertools
import numpy as np
import pandas as pd


from sklearn.neural_network import MLPRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, make_scorer



# cac tham so mo hinh de thay doi trong qua trinh tuning
# khai bao so luong node chmoi lop an
HIDDEN_LAYER_SIZES = [(100, 200, 300), (400, 500, 600, 700)]
# khai bao gia tri cho L2 regularization (de tranh overfitting - tuy nhien dat alpha qua lon se xay ra ra underfitting)
ALPHA = [0.0001]
# khai bao so luong diem du lieu duoc dung huan luyen qua moi lan lap. Mac dinh auto=min(200, so diem du lieu)
BATCH_SIZE = ['auto']
# khai bao toc do hoc ban dau
LEARNING_RATE_INIT = [0.001]
# khai bao so lan lap lon nhat cho huan luyen. Viec huan luyen se duoc lap lai den khi hoi tu hoac dat du so lan duoc khai bao
MAX_ITER = [200]


# Cac tham so cua ham thuc hien qua trinh tuning
# Duong dan toi file du lieu 
DATA_PATH = './data_dr.txt' # 'E:/input_data.csv'

# Bien doc lap. Truyen mang ten cac bien trong file du lieu
VARIABLES = ['X1','X2','X3','X4','X5','X6']

# Bien phu thuoc (1 hoac nhieu bien)
TARGET = 'Y'

# Cac gia tri co the: rmse - sai so quân phương, mae - sso tuong dong
METRIC = 'rmse'

# Tham so ty le chia tap du lieu lam tap train/test (tập tesse duoc chia theo gia tri x duoc truyen vao)
TEST_SIZE = 0.1

# Tham so nguong sai so chap nhan duoc de xet dieu kien cho ham tuning (theo yeu cau)
ERR_THRESH = 0.05



# Ham tinh gia tri sai so quan phuong
def root_mean_squared_error(x, y):
	return np.sqrt(mean_squared_error(x, y))

# Ham tinh gia tri sai so tuyet doi
def mean_absolute_error(x, y):
	return np.mean(np.abs(x - y))




def tuning(data_path, variables, target, test_size, param_grid, n_iters=100, err_thresh=0, scoring='rmse', randomize=False):
	data = pd.read_csv(data_path, delimiter='\s+')
	print(data[VARIABLES],data[TARGET])   # Me

	X = data[variables]
	y = data[target]
	## chia tap du lieu thanh tap train va test
	x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=test_size, random_state=42)

	## Tao bien luu to hop gia tri cho ket qua tot nhat va bien luu gia tri giam error tot nhat
	best_params = []
	best_err_drop = np.inf
	## Tao ra cac to hop gia tri tham so de xet lan luot
	keys, values = zip(*param_grid.items())
	permutations_dicts = [dict(zip(keys, v)) for v in itertools.product(*values)]

	## Kiem tra so luong lan lap va so luong to hop gia tri tham so, neu so lan lap lon hon thi gan gia tri chay cho so luong to hop gia tri
	iterations = 0
	if n_iters > len(permutations_dicts):
		iterations = len(permutations_dicts)
	else:
		iterations = n_iters

	## Duyet qua cac to hop tham so va thuc hien viec danh gia mo hinh voi tung to hop
	for i in range(iterations):
		print('iter {}'.format(i))
		# Tao bien luu to hop tham so hien tai
		cur_params = permutations_dicts[i]
		print(cur_params)
		# Tao mo hinh mang neuron 
		model = MLPRegressor(**cur_params)
		try:
			# Huan luyen mo hinh voi tap x, y train
			model.fit(x_train, y_train)
			# Uoc tinh cac tap gia tri y pred tu tap cac gia tri x su dung mo hinh
			y_train_pred = model.predict(x_train)
			y_test_pred = model.predict(x_test)
			
			# Tao cac bien luu cac gia tri error
			test_error = train_error = 0
			# Tinh cac gia tri Error theo chi so error duoc khai bao (co the mo rong ra cac chi so error khac)
			if scoring == 'rmse':
				train_error = root_mean_squared_error(y_train_pred, y_train)
				test_error = root_mean_squared_error(y_test_pred, y_test)
			elif scoring == 'mae':
				train_error = mean_absolute_error(y_train_pred, y_train)
				test_error = mean_absolute_error(y_test_pred, y_test)
			else:
				print('invalid input for metric name')
				return None
			# Tinh do giam error theo hieu error tap test tru tap train 
			cur_err_drop = test_error - train_error
			print('cur_err_drop: ',cur_err_drop)  # Me
			# Xet cac dieu kien do giam error theo de bai, neu thoa man thi dung vong lap va tra ve tap tham so thoa man
			if cur_err_drop <= 0 or cur_err_drop < err_thresh:
				print('found param set that satisfies the current threshold with error drop between test and train:', cur_err_drop)
				best_params = cur_params
				break
			# Neu dieu kien tien quyet cua de bai duoc thoa man, tiep tuc so sanh voi do giam error tot nhat hien co
			# dieu kien nay nham dam bao ket qua tra ve tot nhat co the khi vong lap dung
			if cur_err_drop < best_err_drop:
				best_err_drop = cur_err_drop
				best_params = cur_params
				best_err_train = train_error   # Me
				best_err_test = test_error    # Me
				print('found new param set with better error drop')
		except:
			print('PARAM SET PRODUCED A MODEL THAT GIVES INVALID VALUES, THEREFORE IS DISQUALIFIED')
			pass
	print('tuning process end after {} iterations.'.format(i+1))
	print(best_params)
	# Tao mo hinh voi cac tham so ket qua
	best_model = MLPRegressor(**best_params)
	best_model.fit(X, y)
	# In cac ket qua mo hinh tot nhat
	print('model\'s coefficient of determination when trained with train dataset:', best_model.score(x_train, y_train))
	print('model\'s coefficient of determination when trained with test dataset:', best_model.score(x_test, y_test))
	print('model\'s ' + METRIC + ' error on the train dataset:', best_err_train)
	print('model\'s ' + METRIC + ' error on the test dataset:', best_err_test)
	print('model\'s ' + METRIC + ' error drop:', best_err_test - best_err_train)

	results = pd.DataFrame(best_params)
	results.to_csv('output_data.csv', index=False)
	return best_params



# Tap cac tham so de tuning mo hinh
PARAMS = {'hidden_layer_sizes': HIDDEN_LAYER_SIZES,
	'activation': ['identity', 'logistic', 'tanh', 'relu'],
	'solver': ['lbfgs', 'adam', 'sgd'],
	'alpha': ALPHA,
	'batch_size': BATCH_SIZE,
	'learning_rate_init': LEARNING_RATE_INIT,
	'max_iter': MAX_ITER,
	'shuffle': [True, False],
}


# code test ham tuning tu tao
best_params = tuning(DATA_PATH, VARIABLES, TARGET, test_size=TEST_SIZE, param_grid=PARAMS, err_thresh=ERR_THRESH)

