import numpy as np
import pandas as pd 
from matplotlib import pyplot as plt
import argparse
import sys

def absoluteOrientationMatrix(D, M):
    dmean = D.mean(1)
    mmean = M.mean(1)
    
    print("dmean is: ", dmean[:, np.newaxis])
    print("mmean is: ", mmean[:, np.newaxis])

    D_centr = D - dmean[:, np.newaxis]
    M_centr = M - mmean[:, np.newaxis]
    
    print("D_centr is: ", D_centr)
    print("M_centr is: ", M_centr)

    # Comment with reference (1).
    # If you don't understand what's written here, plese refer to the links and the code below
    H = np.zeros(shape=(3, 3))
    for column in range(D.shape[1]):
        H += np.outer(D_centr[:, column],  M_centr[:, column])
#         print("H is: ", H)
    print("H is: ", H.T)
    
    u, s, vh = np.linalg.svd(H.T)
    #R = vh @ u.T
    I = np.identity(3)
    if(np.linalg.det(u) * np.linalg.det(vh)<0):
        I[2,2] = -1
    R = u @ I @ vh 
#     R = R.T
    
    # Output of rotmodel has to be the vector. 
    # Comment with reference (2).
    rotmodel = R @ M_centr
#     print(rotmodel)
    dots = 0.0
    norms = 0.0

    for column in range(D_centr.shape[1]):
        dots += np.dot(D_centr[:,column].transpose(),rotmodel[:,column])
        normi = np.linalg.norm(M_centr[:,column])
        norms += normi*normi
    
    # s has to be the scalar.
    s = float(dots/norms)
    
    T = dmean - R @ mmean
    T_GT = dmean - s * R @ mmean

    print("The Rotation matrix is: \n", R, "\n")
    print("The Translation matrix is: \n", T, "\n")
    print("The scale factor is: ", s)
    
    return R, T, T_GT, s


def find_closest_times(res, gt, difference):
    gt_adapted = pd.DataFrame({"time[s]":[0], "tx":[0], "ty":[0], "tz":[0], "qx":[0], "qy":[0], "qz":[0], "qw":[0]})
    gt_adapted = gt_adapted.drop(0)

    res = res.rename(columns={"x":"tx", "y":"ty", "z":"tz"})
    res_initial = res.copy()
    res = res_initial[0:0]

    # Последний момент времени в res неадкватный, поэтому уберем его: 
    # res = res.drop(len(res)-1)
    all_gt_times = gt["# timestamp[ns]"].to_numpy()
    
    for i in range(len(res_initial)):
        time_res = res_initial.iloc[i][0]
        compar_res = np.where(all_gt_times < time_res)
    #     print(compar_res[0])
        if not compar_res[0].shape[0] == 0:
            gt_time = gt.iloc[compar_res[0][-1]][0]
    #     print(abs(gt_time - time_res) < difference)
        if ( (not compar_res[0].shape[0] == 0) and (abs(gt_time - time_res) < difference) ):
            index = compar_res[0][-1]
        else:
            continue

    #     print(index)
        closest_row = gt.iloc[index]
        closest_row = closest_row.rename({ "# timestamp[ns]":"time[s]" })
        closest_row = closest_row.rename(i)
    #     print(closest_row)
        gt_adapted = gt_adapted.append(closest_row)
        res = res.append(res_initial.iloc[i])
    
    return res, gt_adapted


def ATE_error(D, M):
    diff = D.T - M.T
    print("alignmentGT resuls is: \n", diff)
    trans_error1 = np.sqrt(np.sum(np.multiply(diff,diff), 0))
    error1 = np.sqrt(np.dot(trans_error1, trans_error1)/len(trans_error1))
    return error1


def plot_and_save(M, D, name):
    plot = plt.figure(figsize=(10,10))
    ax1 = plot.add_subplot(1, 1, 1)
    ax1.grid()
    # ax1.set_aspect("equal")
    ax1.plot(M[:, 0], M[:, 1], label="estimated")
    ax1.plot(D[:, 0],  D[:, 1], label="ground truth")
    plot.legend()
    plot.savefig(name)


def read_file(filename, sep_=",", switcher=0):
    file = pd.read_csv(filename, sep=sep_)
    if switcher == "1":
        file = file.rename(columns={ "#timestamp [ns]":"# timestamp[ns]", " p_RS_R_x [m]":"tx", " p_RS_R_y [m]":"ty",
                   " p_RS_R_z [m]":"tz", " q_RS_w []":"qw", " q_RS_x []":"qx", " q_RS_y []":"qy",
                        " q_RS_z []":"qz" })
    return file
    

if __name__ == "__main__":
    
    parser = argparse.ArgumentParser(description='''This script computes the absolute trajectory error from the ground truth trajectory and the estimated trajectory. ''')
    parser.add_argument("first_file", help='ground truth trajectory (format: # timestamp[ns],tx,ty,tz,qw,qx,qy,qz) or (format: #timestamp [ns], p_RS_R_x [m], p_RS_R_y [m], p_RS_R_z [m], q_RS_w [], q_RS_x [], q_RS_y [], q_RS_z []). Use --switch_gt=0 to use first format or --switch_gt=1 to use second format')
    parser.add_argument("second_file", help="estimated trajectory (format: time[s] x y z qx qy qz qw)")
    parser.add_argument("--switch_gt", help="Use --switch_gt=0 to use first format or --switch_gt=1 to use second format", default=0)
    parser.add_argument("--slam_name", help="the name of the slam used for the processing. ORB-SLAM3 by default", default="orb_slam3")
    parser.add_argument("--savefile_folder", help="absolute or relative path to save the file", default=".")

    args = parser.parse_args()
    first_file = args.first_file
    switcher = args.switch_gt
    second_file = args.second_file
    
    gt = read_file(first_file, switcher=switcher)
    res = read_file(second_file, sep_=" ")
    
    tmp = gt["qw"]
    del gt["qw"]
    gt["qw"] = tmp
    gt
    
    # by default is 20 mlns of ns
    difference = 20000000
    res, gt_adapted = find_closest_times(res, gt, difference)
    
    D = gt_adapted[["tx", "ty", "tz"]].to_numpy()
    M = res[["tx", "ty", "tz"]].to_numpy()
    
    R, T, T_GT, s = absoluteOrientationMatrix(D.T, M.T)
    M_GT_aligned = (s * R @ M.T).T + T_GT
    M_aligned = (R @ M.T).T + T
    
    error_GT = ATE_error(D, M_GT_aligned)
    error = ATE_error(D, M_aligned)
    
    folder = args.savefile_folder
    f = open(folder + "/" + args.slam_name + "_errors.txt", "w")
    f.write("RMSError using scaling parameter: " + str(error_GT) + " m.")
    f.write("\n")
    f.write("RMSError without scaling parameter: " + str(error) + " m.")
    f.close()
    
    plot_and_save(M_aligned, D, folder + "/" + args.slam_name + "_trajectory.png")
    plot_and_save(M_GT_aligned, D, folder + "/" + args.slam_name + "_trajectory_GT.png")
    
    print("End of processing. Success! Check the created plots and files with data")
