import argparse
import datetime
import os
import time
from configparser import ConfigParser

from agents import *
from envs import *
from utils import *
from torch.multiprocessing import Pipe


import numpy as np
import pickle


ROOT_DIR = os.path.dirname(os.path.abspath(__file__))


def parse_args():
    conf_parser = argparse.ArgumentParser(add_help=False)
    conf_parser.add_argument("-c", "--conf_file",
                             help="Specify config file", metavar="FILE")
    args, remaining_argv = conf_parser.parse_known_args()

    defaults = {}
    if args.conf_file:
        config = ConfigParser()
        config.read([args.conf_file])
        defaults |= dict(config.items("DEFAULT"))

    # Dynamically add arguments from the configuration file
    parser = argparse.ArgumentParser(parents=[conf_parser])
    for key, value in defaults.items():
        # Use the key from the config file as the argument name
        parser.add_argument(f'--{key}', default=value)

    parser.set_defaults(**defaults)
    args = parser.parse_args(remaining_argv)
    args.conf_file = conf_parser.parse_known_args()[0].conf_file

    # Transform args into a SectionProxy
    config_proxy = ConfigParser()
    # config_proxy.add_section('DEFAULT')
    for key, value in vars(args).items():
        config_proxy.set('DEFAULT', key, str(value))

    return config_proxy["DEFAULT"]


def main():
    # print({section: dict(config[section]) for section in config.sections()})
    default_config = parse_args()
    print(dict(default_config))

    env_id = default_config['EnvID']
    env_type = default_config['EnvType']

    if env_type == 'mario':
        env = BinarySpaceToDiscreteSpaceEnv(gym_super_mario_bros.make(env_id), COMPLEX_MOVEMENT)
    elif env_type == 'atari':
        env = gym.make(env_id)
    else:
        raise NotImplementedError
    input_size = env.observation_space.shape  # 4
    output_size = env.action_space.n  # 2

    if 'Breakout' in env_id:
        output_size -= 1

    env.close()

    is_render = False
    # model_path = os.path.join(ROOT_DIR, 'models', f"{env_id}.model")
    # predictor_path = os.path.join(ROOT_DIR, 'models', f"{env_id}.pred")
    # target_path = os.path.join(ROOT_DIR, 'models', f"{env_id}.target")
    model_path = "model"
    predictor_path = "pred"
    target_path = "target"


    use_cuda = True
    use_gae = default_config.getboolean('UseGAE')
    use_noisy_net = default_config.getboolean('UseNoisyNet')

    lam = float(default_config['Lambda'])
    num_worker = int(default_config['NumEnv'])

    num_step = int(default_config['NumStep'])

    ppo_eps = float(default_config['PPOEps'])
    epoch = int(default_config['Epoch'])
    mini_batch = int(default_config['MiniBatch'])
    batch_size = int(num_step * num_worker / mini_batch)
    learning_rate = float(default_config['LearningRate'])
    entropy_coef = float(default_config['Entropy'])
    gamma = float(default_config['Gamma'])
    clip_grad_norm = float(default_config['ClipGradNorm'])

    sticky_action = False
    action_prob = float(default_config['ActionProb'])
    life_done = default_config.getboolean('LifeDone')

    agent = RNDAgent

    if default_config['EnvType'] == 'atari':
        env_type = AtariEnvironment
    elif default_config['EnvType'] == 'mario':
        env_type = MarioEnvironment
    else:
        raise NotImplementedError

    agent = agent(
        input_size,
        output_size,
        num_worker,
        num_step,
        gamma,
        lam=lam,
        learning_rate=learning_rate,
        ent_coef=entropy_coef,
        clip_grad_norm=clip_grad_norm,
        epoch=epoch,
        batch_size=batch_size,
        ppo_eps=ppo_eps,
        use_cuda=use_cuda,
        use_gae=use_gae,
        use_noisy_net=use_noisy_net
    )

    if default_config['LoadModel']:
        print('Loading Pre-trained model....')
        print(f"Loading: {model_path}")
        if use_cuda:
            agent.model.load_state_dict(torch.load(model_path))
            agent.rnd.predictor.load_state_dict(torch.load(predictor_path))
            agent.rnd.target.load_state_dict(torch.load(target_path))
        else:
            agent.model.load_state_dict(torch.load(model_path, map_location='cpu'))
            agent.rnd.predictor.load_state_dict(torch.load(predictor_path, map_location='cpu'))
            agent.rnd.target.load_state_dict(torch.load(target_path, map_location='cpu'))
        print('End load...')

    works = []
    parent_conns = []
    child_conns = []
    for idx in range(num_worker):
        parent_conn, child_conn = Pipe()
        work = env_type(
            env_id,
            is_render,
            idx,
            child_conn,
            sticky_action=sticky_action,
            p=action_prob,
            life_done=life_done,
            use_state_loading=default_config.getboolean("UseStateLoading"),
            should_calc_additional_metrics=False
        )
        work.start()
        works.append(work)
        parent_conns.append(parent_conn)
        child_conns.append(child_conn)

    states = np.zeros([num_worker, 4, 84, 84])

    steps = 0
    rall = 0
    rd = False

    trajectories = {env_idx: [] for env_idx in range(num_worker)}
    trajectiories_dirname = "trajectories_kl"
    os.makedirs(os.path.join(ROOT_DIR, trajectiories_dirname), exist_ok=True)

    saved_tr = 0

    while True:
        if saved_tr>=5_000:
            break

        steps += 1
        actions, value_ext, value_int, policy, action_probs = agent.get_action(np.float32(states) / 255., return_action_prob=True)

        for parent_conn, action in zip(parent_conns, actions):
            parent_conn.send(action)

        next_states, rewards, dones, real_dones, log_rewards, next_obs, current_rooms = [], [], [], [], [], [], []
        for parent_conn in parent_conns:
            s, r, d, rd, lr, cr = parent_conn.recv()
            next_states.append(s)
            rewards.append(r)
            dones.append(d)
            real_dones.append(rd)
            log_rewards.append(lr)
            next_obs.append(s[3, :, :].reshape([1, 84, 84]))
            current_rooms.append(cr)


        for env_idx in range(num_worker):
            trajectories[env_idx].append(
                {
                    'state': states[env_idx][3],
                    'action': actions[env_idx],  # Store the actions taken
                    'action_prob': action_probs[env_idx],
                    'reward': rewards[env_idx],
                    'done': dones[env_idx],
                    'real_done': real_dones[env_idx],
                    'current_room': current_rooms[env_idx]
                }
            )

            if dones[env_idx]:
                print("saving...")
                with open(
                    os.path.join(
                        ROOT_DIR, trajectiories_dirname, f'trajectories{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}.pkl'
                    ),
                    "wb",
                ) as f:
                    pickle.dump(trajectories[env_idx], f)
                trajectories[env_idx]=[]
                saved_tr+=1

        next_states = np.stack(next_states)
        states = next_states[:, :, :, :]

if __name__ == '__main__':
    main()
