'''
Modify from
https://github.com/Kai-46/nerfplusplus/blob/master/data_loader_split.py
'''
import os
import pdb
import glob
import scipy
import imageio
import numpy as np
import torch

########################################################################################################################
# camera coordinate system: x-->right, y-->down, z-->scene (opencv/colmap convention)
# poses is camera-to-world
########################################################################################################################
def find_files(dir, exts):
    if os.path.isdir(dir):
        files_grabbed = []
        for ext in exts:
            files_grabbed.extend(glob.glob(os.path.join(dir, ext)))
        if len(files_grabbed) > 0:
            files_grabbed = sorted(files_grabbed)
        return files_grabbed
    else:
        return []


def load_data_split(split_dir, skip=1, try_load_min_depth=True, only_img_files=False,
                    training_ids=None):

    def parse_txt(filename):
        assert os.path.isfile(filename)
        nums = open(filename).read().split()
        return np.array([float(x) for x in nums]).reshape([4, 4]).astype(np.float32)

    if only_img_files:
        img_files = find_files('{}/rgb'.format(split_dir), exts=['*.png', '*.jpg'])
        return img_files

    # camera parameters files
    intrinsics_files = find_files('{}/intrinsics'.format(split_dir), exts=['*.txt'])
    pose_files = find_files('{}/pose'.format(split_dir), exts=['*.txt'])

    intrinsics_files = intrinsics_files[::skip]
    pose_files = pose_files[::skip]
    cam_cnt = len(pose_files)

    # img files
    img_files = find_files('{}/rgb'.format(split_dir), exts=['*.png', '*.jpg'])
    if len(img_files) > 0:
        img_files = img_files[::skip]
        assert(len(img_files) == cam_cnt)
    else:
        img_files = [None, ] * cam_cnt

    # mask files
    mask_files = find_files('{}/mask'.format(split_dir), exts=['*.png', '*.jpg'])
    if len(mask_files) > 0:
        mask_files = mask_files[::skip]
        assert(len(mask_files) == cam_cnt)
    else:
        mask_files = [None, ] * cam_cnt

    # min depth files
    mindepth_files = find_files('{}/min_depth'.format(split_dir), exts=['*.png', '*.jpg'])
    if try_load_min_depth and len(mindepth_files) > 0:
        mindepth_files = mindepth_files[::skip]
        assert(len(mindepth_files) == cam_cnt)
    else:
        mindepth_files = [None, ] * cam_cnt
    
    # sample by training ids
    if training_ids is not None:
        final_training_ids = []
        for idx, ele in enumerate(intrinsics_files):
            if int(ele.split("/")[-1].replace(".txt", "")) in training_ids:
                final_training_ids.append(idx)
        training_ids = final_training_ids
        training_ids = [id - 1 for id in training_ids]  # image id start with 1
        intrinsics_files = [intrinsics_files[id] for id in training_ids]
        pose_files = [pose_files[id] for id in training_ids]
        img_files = [img_files[id] for id in training_ids]
        mask_files = [mask_files[id] for id in training_ids]
        mindepth_files = [mindepth_files[id] for id in training_ids]
    return intrinsics_files, pose_files, img_files, mask_files, mindepth_files


def rerotate_poses(poses, render_poses):
    poses = np.copy(poses)
    centroid = poses[:,:3,3].mean(0)

    poses[:,:3,3] = poses[:,:3,3] - centroid

    # Find the minimum pca vector with minimum eigen value
    x = poses[:,:3,3]
    mu = x.mean(0)
    cov = np.cov((x-mu).T)
    ev , eig = np.linalg.eig(cov)
    cams_up = eig[:,np.argmin(ev)]
    if cams_up[1] < 0:
        cams_up = -cams_up

    # Find rotation matrix that align cams_up with [0,1,0]
    R = scipy.spatial.transform.Rotation.align_vectors(
            [[0,-1,0]], cams_up[None])[0].as_matrix()

    # Apply rotation and add back the centroid position
    poses[:,:3,:3] = R @ poses[:,:3,:3]
    poses[:,:3,[3]] = R @ poses[:,:3,[3]]
    poses[:,:3,3] = poses[:,:3,3] + centroid
    render_poses = np.copy(render_poses)
    render_poses[:,:3,3] = render_poses[:,:3,3] - centroid
    render_poses[:,:3,:3] = R @ render_poses[:,:3,:3]
    render_poses[:,:3,[3]] = R @ render_poses[:,:3,[3]]
    render_poses[:,:3,3] = render_poses[:,:3,3] + centroid
    return poses, render_poses


def load_nerfpp_data(basedir, rerotate=True, training_ids=None):
    tr_K, tr_c2w, tr_im_path = load_data_split(os.path.join(basedir, 'train'), training_ids=training_ids)[:3]
    assert len(tr_im_path) > 0, f"Images are not found in {basedir}"
    te_K, te_c2w, te_im_path = load_data_split(os.path.join(basedir, 'test'))[:3]
    assert len(tr_K) == len(tr_c2w) and len(tr_K) == len(tr_im_path)
    assert len(te_K) == len(te_c2w) and len(te_K) == len(te_im_path)

    # Determine split id list
    i_split = [[], []]
    i = 0
    for _ in tr_c2w:
        i_split[0].append(i)
        i += 1
    for _ in te_c2w:
        i_split[1].append(i)
        i += 1

    # Load camera intrinsics. Assume all images share a intrinsic.
    K_flatten = np.loadtxt(tr_K[0])
    for path in tr_K:
        assert np.allclose(np.loadtxt(path), K_flatten)
    for path in te_K:
        assert np.allclose(np.loadtxt(path), K_flatten)
    K = K_flatten.reshape(4,4)[:3,:3]

    # Load camera poses
    poses = []
    for path in tr_c2w:
        poses.append(np.loadtxt(path).reshape(4,4))
    for path in te_c2w:
        poses.append(np.loadtxt(path).reshape(4,4))

    # Load images
    imgs = []
    for path in tr_im_path:
        imgs.append(imageio.imread(path) / 255.)
    for path in te_im_path:
        imgs.append(imageio.imread(path) / 255.)

    # Bundle all data
    imgs = np.stack(imgs, 0)
    poses = np.stack(poses, 0)
    i_split.append(i_split[1])
    H, W = imgs.shape[1:3]
    focal = K[[0,1], [0,1]].mean()

    # Generate movie trajectory
    render_poses_path = sorted(glob.glob(os.path.join(basedir, 'camera_path', 'pose', '*txt')))
    render_poses = []
    for path in render_poses_path:
        render_poses.append(np.loadtxt(path).reshape(4,4))
    render_poses = np.array(render_poses)
    render_K = np.loadtxt(glob.glob(os.path.join(basedir, 'camera_path', 'intrinsics', '*txt'))[0]).reshape(4,4)[:3,:3]
    render_poses[:,:,0] *= K[0,0] / render_K[0,0]
    render_poses[:,:,1] *= K[1,1] / render_K[1,1]
    if rerotate:
        poses, render_poses = rerotate_poses(poses, render_poses)
    render_poses = torch.Tensor(render_poses)
    return imgs, poses, render_poses, [H, W, focal], K, i_split
