import argparse
import os
#os.environ["CUDA_DEVICE_ORDER"]="PCI_BUS_ID"
#os.environ["CUDA_VISIBLE_DEVICES"]="0"

import torch
from exp.exp_main import Exp_Main
import random
import numpy as np

fix_seed = 2021
random.seed(fix_seed)
torch.manual_seed(fix_seed)
np.random.seed(fix_seed)
#torch.backends.cudnn.benchmark = False

parser = argparse.ArgumentParser(description='Autoformer & Transformer family for Time Series Forecasting')

# basic config
parser.add_argument('--is_training', type=int, default=0, help='status')
parser.add_argument('--model_id', type=str, default='test', help='model id')
parser.add_argument('--model', type=str, default='MLP',
                    help='model name, options: [Autoformer, Informer, Transformer, DLinear,MLP,Linear]')

# data loader # thor 2ep train diff=1, ngsim 2rp train lr>=0.03 diff=1,  assembly 2ep train diff=0 lr~0.1, kinova 1ep, diff=1
parser.add_argument('--data_name', type=str, default='kinova', help='dataset type') #['etth,'ill,'exchange','thor','ngsim','ford','ill_1d','sin','assembly', 'kinova']
parser.add_argument('--data', type=str, default='custom', help='dataset type')
parser.add_argument('--root_path', type=str, default='./dataset/', help='root path of the data file')
parser.add_argument('--data_path', type=str, default='exchange_rate.csv', help='data file')
parser.add_argument('--features', type=str, default='M',
                    help='forecasting task, options:[M, S, MS]; M:multivariate predict multivariate, S:univariate predict univariate, MS:multivariate predict univariate')
parser.add_argument('--target', type=str, default='OT', help='target feature in S or MS task')
parser.add_argument('--freq', type=str, default='h',
                    help='freq for time features encoding, options:[s:secondly, t:minutely, h:hourly, d:daily, b:business days, w:weekly, m:monthly], you can also use more detailed freq like 15min or 3h')
parser.add_argument('--checkpoints', type=str, default='./results/checkpoints/', help='location of model checkpoints')

# forecasting task
parser.add_argument('--seq_len', type=int, default=96, help='input sequence length')
parser.add_argument('--label_len', type=int, default=48, help='start token length')
parser.add_argument('--pred_len', type=int, default=192, help='prediction sequence length')  # 96

parser.add_argument('--pred_diff', type=int, default=1, help='prediction of difference or not')  # 0
#mse:0.01240613218396902, mae:0.04043710231781006
# adaptation
parser.add_argument("--adapt_layer", default='norm', type=str,choices=['all', 'norm']) # norm
parser.add_argument("--adapt", default='sgd', type=str, choices=['none', 'sgd', 'nrls'])
parser.add_argument("--adapt_batch", default=1, type=int)
parser.add_argument("--update_step", default=5, type=int) # 5
parser.add_argument("--update_step_replay", default=5, type=int) # 5
parser.add_argument("--feedback", default=0, type=int,)
parser.add_argument("--lr", default=0.1, type=float)  # 0.1 0.01 ## 0.120
parser.add_argument("--p0", default=0.01, type=float)
parser.add_argument("--eps", default=1e-5, type=float)
parser.add_argument("--lbd", default=1, type=float)
parser.add_argument("--lr_replay", default=None, type=float)
parser.add_argument("--p0_replay", default=None, type=float)
parser.add_argument("--eps_replay", default=None, type=float)
parser.add_argument("--lbd_replay", default=None, type=float)
parser.add_argument("--replay_buffer", default='feature_sim', choices=['none', 'random','random_time', 'time_decay', 'feature_sim'],
                    type=str) # diff 1  0.05160 0.046
parser.add_argument("--replay_feature", default='traj', choices=['traj', 'feat'],
                    type=str)
parser.add_argument("--buffer_size", default=1000, type=int) #1000
parser.add_argument("--sim_update_thresh", default=0.2, type=float)  # using most similar data to update the model
parser.add_argument("--K0", default=0.1, type=float)  # initial uncertainty factor , ~ K-Lipschitz
parser.add_argument("--K0_step", default=1.01, type=float)  # initial uncertainty factor , ~ K-Lipschitz
parser.add_argument("--delta", default=1e-6, type=float)  # initial uncertainty factor , ~ K-Lipschitz
parser.add_argument("--uncertainty_ratio", default=0.9, type=float)
parser.add_argument("--prior_thresh", default=100, type=float)#None
parser.add_argument("--fitting_thresh", default=1e-4, type=float) #None, 1e-4 ##################33
parser.add_argument("--max_fit_iter", default=1, type=float) #1，10 #######################

# DLinear
parser.add_argument('--individual', action='store_true', default=False,
                    help='DLinear: a linear layer for each variate(channel) individually')
# Formers
parser.add_argument('--embed_type', type=int, default=0,
                    help='0: default 1: value embedding + temporal embedding + positional embedding 2: value embedding + temporal embedding 3: value embedding + positional embedding 4: value embedding')
parser.add_argument('--enc_in', type=int, default=8,
                    help='encoder input size')  # DLinear with --individual, use this hyperparameter as the number of channels
parser.add_argument('--dec_in', type=int, default=8, help='decoder input size')
parser.add_argument('--c_out', type=int, default=8, help='output size')
parser.add_argument('--d_model', type=int, default=128, help='dimension of model') #512
parser.add_argument('--n_heads', type=int, default=8, help='num of heads')
parser.add_argument('--e_layers', type=int, default=2, help='num of encoder layers')
parser.add_argument('--d_layers', type=int, default=1, help='num of decoder layers')
parser.add_argument('--d_ff', type=int, default=2048, help='dimension of fcn')
parser.add_argument('--moving_avg', type=int, default=25, help='window size of moving average')
parser.add_argument('--factor', type=int, default=3, help='attn factor')  # 1
parser.add_argument('--distil', action='store_false',
                    help='whether to use distilling in encoder, using this argument means not using distilling',
                    default=True)
parser.add_argument('--dropout', type=float, default=0.05, help='dropout')
parser.add_argument('--embed', type=str, default='timeF',
                    help='time features encoding, options:[timeF, fixed, learned]')
parser.add_argument('--activation', type=str, default='gelu', help='activation')
parser.add_argument('--output_attention', action='store_true', help='whether to output attention in ecoder')
parser.add_argument('--do_predict', action='store_true', default=False, help='whether to predict unseen future data')

# optimization #0.20526212453842163
parser.add_argument('--num_workers', type=int, default=0, help='data loader num workers')  # 10
parser.add_argument('--itr', type=int, default=1, help='experiments times')  # 2
parser.add_argument('--train_epochs', type=int, default=2, help='train epochs') #10
parser.add_argument('--batch_size', type=int, default=32, help='batch size of train input data')
parser.add_argument('--patience', type=int, default=3, help='early stopping patience')
parser.add_argument('--learning_rate', type=float, default=0.0001, help='optimizer learning rate')  # 0.0001  0.01 0.0005
parser.add_argument('--des', type=str, default='test', help='exp description')
parser.add_argument('--loss', type=str, default='mse', help='loss function')
parser.add_argument('--lradj', type=str, default='type1', help='adjust learning rate')
parser.add_argument('--use_amp', action='store_true', help='use automatic mixed precision training', default=False)

# GPU
parser.add_argument('--use_gpu', type=int, default=1, help='use gpu')  # True
parser.add_argument('--gpu', type=int, default=0, help='gpu')
parser.add_argument('--use_multi_gpu', action='store_true', help='use multiple gpus', default=False)
parser.add_argument('--devices', type=str, default='0,1,2,3', help='device ids of multi gpus')
parser.add_argument('--test_flop', action='store_true', default=False, help='See utils/tools for usage')

args = parser.parse_args()
if 'former' in args.model:
	args.d_model = 512
	args.learning_rate = 0.0001
elif 'MLP' in args.model:
	args.d_model = 128
	args.learning_rate = 0.001
	if args.data_name in ['thor','ngsim']:
		args.learning_rate = 0.003

if args.p0_replay is None:
	args.p0_replay = args.p0
if args.eps_replay is None:
	args.eps_replay = args.eps
if args.lbd_replay is None:
	args.lbd_replay = args.lbd
if args.lr_replay is None:
	args.lr_replay = args.lr

if args.prior_thresh is None and 'thor' in args.data_name:
	args.prior_thresh=10

if 'exchange' in args.data_name:
	args.data = 'custom'
	args.data_path='exchange_rate.csv'
	args.features ='M'
	args.seq_len = 96
	args.label_len = 48
	args.pred_len = 192
	args.enc_in=8
	args.dec_in = 8
	args.c_out = 8
elif 'etth' in args.data_name:
	args.data = 'ETTh1'
	args.data_path='ETTh1.csv'
	args.features = 'S'
	args.seq_len = 192
	args.label_len = 48
	args.pred_len = 192
	args.enc_in=1
	args.dec_in = 1
	args.c_out = 1
elif 'ill' in args.data_name:
	args.data = 'custom'
	args.data_path='national_illness.csv'
	if '1d' not in args.data_name:
		args.features = 'M'
		args.seq_len = 36
		args.label_len = 18
		args.pred_len = 36
		args.enc_in = 7
		args.dec_in = 7
		args.c_out = 7
	else:
		args.features = 'S'
		args.seq_len = 10
		args.label_len = 1
		args.pred_len = 10
		args.enc_in = 1
		args.dec_in = 1
		args.c_out = 1

elif 'thor' in args.data_name:
	args.data = 'thor'
	args.data_path='thor_data_prep.pkl'
	args.features = 'M'
	args.seq_len = 20
	args.label_len = 10
	args.pred_len = 40
	args.enc_in=2
	args.dec_in = 2
	args.c_out = 2
	args.batch_size = 128
elif 'ngsim' in args.data_name:
	args.data = 'ngsim'
	args.data_path='vehicle_ngsim.pkl'
	args.features = 'M'
	args.seq_len = 15
	args.label_len = 10
	args.pred_len = 30
	args.enc_in=2
	args.dec_in = 2
	args.c_out = 2
	args.batch_size = 128
elif 'assembly' in args.data_name:
	args.data = 'assembly'
	args.data_path='human_assembly.pkl'
	args.features = 'M'
	args.seq_len = 20
	args.label_len = 10
	args.pred_len = 40
	args.enc_in=12
	args.dec_in = 12
	args.c_out = 12
elif 'kinova' in args.data_name:
	args.data = 'kinova'
	args.data_path='kinova_monitor.pkl'
	args.features = 'M'
	args.seq_len = 20
	args.label_len = 10
	args.pred_len = 50
	args.enc_in= 3
	args.dec_in = 3
	args.c_out = 3
	args.batch_size = 128

elif 'ford' in args.data_name:
	args.data = 'ford'
	args.data_path='ford_data_prep.pkl'
	args.features = 'M'
	args.seq_len = 20
	args.label_len = 10
	args.pred_len = 20
	args.enc_in=3
	args.dec_in = 3
	args.c_out = 3
	args.batch_size = 128

elif 'sin' in args.data_name:
	args.data = 'sin'
	args.features = 'S'
	args.seq_len = 5
	args.label_len = 1
	args.pred_len = 40
	args.enc_in = 1
	args.dec_in = 1
	args.c_out = 1

args.use_gpu = True if torch.cuda.is_available() and args.use_gpu else False

if args.use_gpu and args.use_multi_gpu:
	args.dvices = args.devices.replace(' ', '')
	device_ids = args.devices.split(',')
	args.device_ids = [int(id_) for id_ in device_ids]
	args.gpu = args.device_ids[0]

print('Args in experiment:')
print(args)

Exp = Exp_Main

if args.is_training:
	for ii in range(args.itr):
		# setting record of experiments
		setting = '{}_{}/{}_sl{}_ll{}_pl{}_diff{}_{}'.format(
			args.data_name,
			args.features,
			args.model,
			args.seq_len,
			args.label_len,
			args.pred_len, args.pred_diff, ii)
		exp = Exp(args)  # set experiments
		print('>>>>>>>start training : {}>>>>>>>>>>>>>>>>>>>>>>>>>>'.format(setting))
		exp.train(setting)

		print('>>>>>>>testing : {}<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'.format(setting))
		#exp.test(setting)

		if args.do_predict:
			print('>>>>>>>predicting : {}<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'.format(setting))
			#exp.predict(setting, True)

		torch.cuda.empty_cache()
else:
	ii = 0
	setting = '{}_{}/{}_sl{}_ll{}_pl{}_diff{}_{}'.format(
		args.data_name,
		args.features,
		args.model,
		args.seq_len,
		args.label_len,
		args.pred_len, args.pred_diff, ii)

	exp = Exp(args)  # set experiments
	#exp.test(setting,test=1)
	adapt_layers = []
	all_weight_cnt, adapt_weight_cnt = 0, 0
	for name, p in exp.model.named_parameters():
		if 'former' in args.model:
			#if 'decoder.projection' in name:
			#	adapt_layers.append(name)
			#	adapt_weight_cnt += p.data.nelement()
			if args.adapt_layer =='norm':
				if 'decoder.' in name and 'norm' in name:
					adapt_layers.append(name)
					adapt_weight_cnt += p.data.nelement()
			elif args.adapt_layer == 'all':
				adapt_layers.append(name)
				adapt_weight_cnt += p.data.nelement()
			#elif 'decoder.layers.0.cross_attention.out_projection' in name:
			#	adapt_layers.append(name)
			#	adapt_weight_cnt += p.data.nelement()
		else:
			if args.adapt_layer == 'norm':
				if 'norm' in name : # or 'decoder.' in name
					adapt_layers.append(name)
					adapt_weight_cnt += p.data.nelement()
			elif args.adapt_layer == 'all':
				adapt_layers.append(name)
				adapt_weight_cnt += p.data.nelement()
		#print(name, p.size())
		all_weight_cnt += p.data.nelement()
	param_num = all_weight_cnt / 1024. / 1024.
	adapt_num = adapt_weight_cnt / 1024. / 1024.
	print("Number of model parameters: {} M, adaptable parameters: {} M".format(param_num, adapt_num))

	load_path = os.path.join(args.checkpoints + setting, 'checkpoint.pth')
	#load_path = None
	print('>>>>>>>adaptable prediction : {}<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'.format(setting))
	# setting = setting + '/{}_{}_adapt{}/st{}_sgd{}_nrls{}_{}_mix{}_{}_fb{}_bfsz{}'.format(
	# 	args.adapt,
	# 	args.replay_buffer,args.adapt_layer, args.update_step_replay,args.lr,args.p0, args.eps,args.prior_thresh, args.sim_update_thresh,args.feedback,args.buffer_size)
	setting = setting + '/{}_{}_adapt{}/st{}_sgd{}_bfsz{}'.format(
		args.adapt,
		args.replay_buffer,args.adapt_layer, args.update_step_replay,args.lr,args.buffer_size)
	stop_iter=-1
	if args.adapt_batch<=1:
		exp.adaptable_predict(setting, adapt_layers=adapt_layers, load_path=load_path,stop_iter=stop_iter)
	else:
		exp.adaptable_predict_simple_batch(setting, adapt_layers=adapt_layers, load_path=load_path, stop_iter=stop_iter)

	torch.cuda.empty_cache()


'''
diff=0
none mse:0.06373938173055649 traj_mse:0.46025368571281433
fb 
step=5  mse:0.05533579736948013 traj_mse:0.4041535556316376,
step=1 mse:0.059162039309740067 traj_mse:0.42955881357192993


diff=1
none  mse:4.5384955406188965 traj_mse:0.3838249742984772,
fb
step=5 mse:4.5402913093566895 traj_mse:0.3829996585845947
step=1 mse:4.537436008453369 traj_mse:0.3820759356021881
ff
step=1  mse:4.538215160369873  traj_mse:0.38199329376220703
step=5  mse:4.535934925079346 traj_mse:0.3803751766681671
lr=0.01 traj_mse:0.3798224627971649
'''