#pragma once
#ifndef _UTILITY_LIDAR_ODOMETRY_H_
#define _UTILITY_LIDAR_ODOMETRY_H_

#include <ros/ros.h>

#include <std_msgs/Header.h>
#include <std_msgs/Float64MultiArray.h>
#include <sensor_msgs/Imu.h>
#include <sensor_msgs/PointCloud2.h>
#include <sensor_msgs/NavSatFix.h>
#include <nav_msgs/Odometry.h>
#include <nav_msgs/Path.h>
#include <visualization_msgs/Marker.h>
#include <visualization_msgs/MarkerArray.h>

#include <opencv2/opencv.hpp>

#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/search/impl/search.hpp>
#include <pcl/range_image/range_image.h>
#include <pcl/kdtree/kdtree_flann.h>
#include <pcl/common/common.h>
#include <pcl/common/transforms.h>
#include <pcl/registration/icp.h>
#include <pcl/io/pcd_io.h>
#include <pcl/filters/filter.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/filters/crop_box.h> 
#include <pcl_conversions/pcl_conversions.h>

#include <tf/LinearMath/Quaternion.h>
#include <tf/transform_listener.h>
#include <tf/transform_datatypes.h>
#include <tf/transform_broadcaster.h>
 
#include <vector>
#include <cmath>
#include <algorithm>
#include <queue>
#include <deque>
#include <iostream>
#include <fstream>
#include <ctime>
#include <cfloat>
#include <iterator>
#include <sstream>
#include <string>
#include <limits>
#include <iomanip>
#include <array>
#include <thread>
#include <mutex>

using namespace std;

typedef pcl::PointXYZI PointType;

// 传感器型号
enum class SensorType { VELODYNE, OUSTER };

class ParamServer
{
public:

    ros::NodeHandle nh;

    std::string robot_id;

    // 话题
    string pointCloudTopic; // points_raw 原始点云数据
    string imuTopic;        // imu_raw 对应park数据集，imu_correct对应outdoor数据集，都是原始imu数据，不同的坐标系表示
    string odomTopic;       // odometry/imu，imu里程计，imu积分计算得到
    string gpsTopic;        // odometry/gps，gps里程计

    // 坐标系
    string lidarFrame;      // 激光坐标系
    string baselinkFrame;   // 载体坐标系
    string odometryFrame;   // 里程计坐标系
    string mapFrame;        // 世界坐标系

    // GPS参数
    bool useImuHeadingInitialization;   //
    bool useGpsElevation;
    float gpsCovThreshold;
    float poseCovThreshold;

    // 保存PCD
    bool savePCD;               // 是否保存地图
    string savePCDDirectory;    // 保存路径

    // 激光传感器参数
    SensorType sensor;      // 传感器型号
    int N_SCAN;             // 扫描线数，例如16、64
    int Horizon_SCAN;       // 扫描一周计数，例如每隔0.2°扫描一次，一周360°可以扫描1800次
    int downsampleRate;     // 扫描线降采样，跳过一些扫描线
    float lidarMinRange;    // 最小范围
    float lidarMaxRange;    // 最大范围

    // IMU参数
    float imuAccNoise;          // 加速度噪声标准差
    float imuGyrNoise;          // 角速度噪声标准差
    float imuAccBiasN;          //
    float imuGyrBiasN;
    float imuGravity;           // 重力加速度
    float imuRPYWeight;
    vector<double> extRotV;
    vector<double> extRPYV;
    vector<double> extTransV;
    Eigen::Matrix3d extRot;     // xyz坐标系旋转
    Eigen::Matrix3d extRPY;     // RPY欧拉角的变换关系
    Eigen::Vector3d extTrans;   // xyz坐标系平移
    Eigen::Quaterniond extQRPY;

    // LOAM
    float edgeThreshold;
    float surfThreshold;
    int edgeFeatureMinValidNum;
    int surfFeatureMinValidNum;

    // voxel filter paprams
    float odometrySurfLeafSize;
    float mappingCornerLeafSize;
    float mappingSurfLeafSize ;

    float z_tollerance; 
    float rotation_tollerance;

    // CPU Params
    int numberOfCores;
    double mappingProcessInterval;

    // Surrounding map
    float surroundingkeyframeAddingDistThreshold; 
    float surroundingkeyframeAddingAngleThreshold; 
    float surroundingKeyframeDensity;
    float surroundingKeyframeSearchRadius;
    
    // Loop closure
    bool  loopClosureEnableFlag;
    float loopClosureFrequency;
    int   surroundingKeyframeSize;
    float historyKeyframeSearchRadius;
    float historyKeyframeSearchTimeDiff;
    int   historyKeyframeSearchNum;
    float historyKeyframeFitnessScore;

    // global map visualization radius
    float globalMapVisualizationSearchRadius;
    float globalMapVisualizationPoseDensity;
    float globalMapVisualizationLeafSize;


    //liauto param
    string laneletFilePath;
    string gpsOriTopic;
    int relocationKeyMapSize;
    int relocationRotateGap;
    float scanToMapGap;
    float turnScale;
    int testTorque;
    int testBreak;
    int useIcp;
    float ndtTransformationEpsilon;
    int resetImuPreintegration;
    float icpSetMaxCorrespondenceDistance;
    float ndtSetStepSize;
    float ndtSetResolution;
    float icpSetTransformationEpsilon;
    float icpSetEuclideanFitnessEpsilon;
    int icpsetMaximumIterations;
    int icpSetRANSACIterations;
    int relocationPickGap;
    float kalman_initial_estimate;
    float kalman_initial_error;
    float kalman_measurement_error;
    float kalman_process_error;
    float testSpeed;
    float testMinProgDist;
    float laneletMapScale;
    float laneletMapR,laneletMapG,laneletMapB;
    float markerDangerAreaScaleX,markerDangerAreaScaleY,markerDangerAreaScaleZ;
    float markerDangerAreaScaleR,markerDangerAreaScaleG,markerDangerAreaScaleB,markerDangerAreaScaleA;
    float markerDangerAreaScaleXoffset,markerDangerAreaScaleZoffset;
    float markerDangerAreaBoundaryPointsSize;
    int obstaclePointsThreshold;
    float obstacleMarkerScale;
    float obstacleMarkerR,obstacleMarkerG,obstacleMarkerB;
    float obstacleMarkerLifetime;
    float obstacleMarkerAlpha;
    float followPointScale;
    float miniDistance;
    float initial_GPS_lat;
    float initial_GPS_lon;

    ParamServer()
    {
        nh.param<std::string>("/robot_id", robot_id, "roboat");

        // 从param server中读取key为"lio_sam/pointCloudTopic"对应的参数，存pointCloudTopic，第三个参数是默认值
        // launch文件中定义<rosparam file="$(find lio_sam)/config/params.yaml" command="load" />，从yaml文件加载参数
        nh.param<std::string>("lio_sam/pointCloudTopic", pointCloudTopic, "points_raw");
        nh.param<std::string>("lio_sam/imuTopic", imuTopic, "imu_correct");
        nh.param<std::string>("lio_sam/odomTopic", odomTopic, "odometry/imu");
        nh.param<std::string>("lio_sam/gpsTopic", gpsTopic, "odometry/gps");

        nh.param<std::string>("lio_sam/lidarFrame", lidarFrame, "base_link");
        nh.param<std::string>("lio_sam/baselinkFrame", baselinkFrame, "base_link");
        nh.param<std::string>("lio_sam/odometryFrame", odometryFrame, "odom");
        nh.param<std::string>("lio_sam/mapFrame", mapFrame, "map");

        nh.param<bool>("lio_sam/useImuHeadingInitialization", useImuHeadingInitialization, false);
        nh.param<bool>("lio_sam/useGpsElevation", useGpsElevation, false);
        nh.param<float>("lio_sam/gpsCovThreshold", gpsCovThreshold, 2.0);
        nh.param<float>("lio_sam/poseCovThreshold", poseCovThreshold, 25.0);

        nh.param<bool>("lio_sam/savePCD", savePCD, false);
        nh.param<std::string>("lio_sam/savePCDDirectory", savePCDDirectory, "/Downloads/LOAM/");

        std::string sensorStr;
        nh.param<std::string>("lio_sam/sensor", sensorStr, "");

        if (sensorStr == "velodyne")
        {
            sensor = SensorType::VELODYNE;
        }
        else if (sensorStr == "ouster")
        {
            sensor = SensorType::OUSTER;
        }
        else
        {
            ROS_ERROR_STREAM(
                "Invalid sensor type (must be either 'velodyne' or 'ouster'): " << sensorStr);
            ros::shutdown();
        }

        nh.param<int>("lio_sam/N_SCAN", N_SCAN, 16);
        nh.param<int>("lio_sam/Horizon_SCAN", Horizon_SCAN, 1800);
        nh.param<int>("lio_sam/downsampleRate", downsampleRate, 1);
        nh.param<float>("lio_sam/lidarMinRange", lidarMinRange, 1.0);
        nh.param<float>("lio_sam/lidarMaxRange", lidarMaxRange, 1000.0);

        nh.param<float>("lio_sam/imuAccNoise", imuAccNoise, 0.01);
        nh.param<float>("lio_sam/imuGyrNoise", imuGyrNoise, 0.001);
        nh.param<float>("lio_sam/imuAccBiasN", imuAccBiasN, 0.0002);
        nh.param<float>("lio_sam/imuGyrBiasN", imuGyrBiasN, 0.00003);
        nh.param<float>("lio_sam/imuGravity", imuGravity, 9.80511);
        nh.param<float>("lio_sam/imuRPYWeight", imuRPYWeight, 0.01);
        nh.param<vector<double>>("lio_sam/extrinsicRot", extRotV, vector<double>());
        nh.param<vector<double>>("lio_sam/extrinsicRPY", extRPYV, vector<double>());
        nh.param<vector<double>>("lio_sam/extrinsicTrans", extTransV, vector<double>());
        extRot = Eigen::Map<const Eigen::Matrix<double, -1, -1, Eigen::RowMajor>>(extRotV.data(), 3, 3);
        extRPY = Eigen::Map<const Eigen::Matrix<double, -1, -1, Eigen::RowMajor>>(extRPYV.data(), 3, 3);
        extTrans = Eigen::Map<const Eigen::Matrix<double, -1, -1, Eigen::RowMajor>>(extTransV.data(), 3, 1);
        extQRPY = Eigen::Quaterniond(extRPY);

        nh.param<float>("lio_sam/edgeThreshold", edgeThreshold, 0.1);
        nh.param<float>("lio_sam/surfThreshold", surfThreshold, 0.1);
        nh.param<int>("lio_sam/edgeFeatureMinValidNum", edgeFeatureMinValidNum, 10);
        nh.param<int>("lio_sam/surfFeatureMinValidNum", surfFeatureMinValidNum, 100);

        nh.param<float>("lio_sam/odometrySurfLeafSize", odometrySurfLeafSize, 0.2);
        nh.param<float>("lio_sam/mappingCornerLeafSize", mappingCornerLeafSize, 0.2);
        nh.param<float>("lio_sam/mappingSurfLeafSize", mappingSurfLeafSize, 0.2);

        nh.param<float>("lio_sam/z_tollerance", z_tollerance, FLT_MAX);
        nh.param<float>("lio_sam/rotation_tollerance", rotation_tollerance, FLT_MAX);

        nh.param<int>("lio_sam/numberOfCores", numberOfCores, 2);
        nh.param<double>("lio_sam/mappingProcessInterval", mappingProcessInterval, 0.15);

        nh.param<float>("lio_sam/surroundingkeyframeAddingDistThreshold", surroundingkeyframeAddingDistThreshold, 1.0);
        nh.param<float>("lio_sam/surroundingkeyframeAddingAngleThreshold", surroundingkeyframeAddingAngleThreshold, 0.2);
        nh.param<float>("lio_sam/surroundingKeyframeDensity", surroundingKeyframeDensity, 1.0);
        nh.param<float>("lio_sam/surroundingKeyframeSearchRadius", surroundingKeyframeSearchRadius, 50.0);

        nh.param<bool>("lio_sam/loopClosureEnableFlag", loopClosureEnableFlag, false);
        nh.param<float>("lio_sam/loopClosureFrequency", loopClosureFrequency, 1.0);
        nh.param<int>("lio_sam/surroundingKeyframeSize", surroundingKeyframeSize, 50);
        nh.param<float>("lio_sam/historyKeyframeSearchRadius", historyKeyframeSearchRadius, 10.0);
        nh.param<float>("lio_sam/historyKeyframeSearchTimeDiff", historyKeyframeSearchTimeDiff, 30.0);
        nh.param<int>("lio_sam/historyKeyframeSearchNum", historyKeyframeSearchNum, 25);
        nh.param<float>("lio_sam/historyKeyframeFitnessScore", historyKeyframeFitnessScore, 0.3);

        nh.param<float>("lio_sam/globalMapVisualizationSearchRadius", globalMapVisualizationSearchRadius, 1e3);
        nh.param<float>("lio_sam/globalMapVisualizationPoseDensity", globalMapVisualizationPoseDensity, 10.0);
        nh.param<float>("lio_sam/globalMapVisualizationLeafSize", globalMapVisualizationLeafSize, 1.0);

        //liauto
        nh.param<std::string>("liauto/laneletFilePath", laneletFilePath, "/Downloads/LOAM/");
        nh.param<std::string>("liauto/gpsOriTopic", gpsOriTopic, "/fix");
        nh.param<int>("liauto/relocationKeyMapSize",relocationKeyMapSize,50);
        nh.param<float>("liauto/scanToMapGap",scanToMapGap,0.1);
        nh.param<float>("liauto/turnScale",turnScale,1.0);
        nh.param<int>("liauto/testTorque",testTorque,250);
        nh.param<int>("liauto/testBreak",testBreak,255);
        nh.param<int>("liauto/useIcp",useIcp,1);
        nh.param<float>("liauto/ndtTransformationEpsilon",ndtTransformationEpsilon,0.5);
        nh.param<int>("liauto/resetImuPreintegration",resetImuPreintegration,0);
        nh.param<float>("liauto/ndtSetStepSize",ndtSetStepSize,0.1);
        nh.param<float>("liauto/ndtSetResolution",ndtSetResolution,0.1);
        nh.param<float>("liauto/icpSetMaxCorrespondenceDistance",icpSetMaxCorrespondenceDistance,200);
        nh.param<float>("liauto/icpSetTransformationEpsilon",icpSetTransformationEpsilon,1.0e-8);
        nh.param<float>("liauto/icpSetEuclideanFitnessEpsilon",icpSetEuclideanFitnessEpsilon,1.0e-8);
        nh.param<int>("liauto/icpsetMaximumIterations",icpsetMaximumIterations,500);
        nh.param<int>("liauto/icpSetRANSACIterations",icpSetRANSACIterations,10);

        nh.param<int>("liauto/relocationPickGap",relocationPickGap,5);

        nh.param<float>("liauto/kalman_initial_estimate",kalman_initial_estimate,0.0);
        nh.param<float>("liauto/kalman_initial_error",kalman_initial_error,0.0);
        nh.param<float>("liauto/kalman_measurement_error",kalman_measurement_error,0.2);
        nh.param<float>("liauto/kalman_process_error",kalman_process_error,0.01);
        nh.param<float>("liauto/testSpeed",testSpeed,3.0);
        nh.param<float>("liauto/testMinProgDist",testMinProgDist,3.0);
        nh.param<float>("liauto/laneletMapScale",laneletMapScale,0.1);
        nh.param<float>("liauto/laneletMapR",laneletMapR,1.0);
        nh.param<float>("liauto/laneletMapG",laneletMapG,0.1);
        nh.param<float>("liauto/laneletMapB",laneletMapB,0.1);

        nh.param<float>("liauto/markerDangerAreaScaleX",markerDangerAreaScaleX,5.0);
        nh.param<float>("liauto/markerDangerAreaScaleY",markerDangerAreaScaleY,5.0);
        nh.param<float>("liauto/markerDangerAreaScaleZ",markerDangerAreaScaleZ,5.0);
        nh.param<float>("liauto/markerDangerAreaScaleXoffset",markerDangerAreaScaleXoffset,5.0);
        nh.param<float>("liauto/markerDangerAreaScaleZoffset",markerDangerAreaScaleZoffset,0.2);
        nh.param<float>("liauto/markerDangerAreaScaleR",markerDangerAreaScaleR,1.0);
        nh.param<float>("liauto/markerDangerAreaScaleG",markerDangerAreaScaleG,0.0);
        nh.param<float>("liauto/markerDangerAreaScaleB",markerDangerAreaScaleB,0.0);
        nh.param<float>("liauto/markerDangerAreaScaleA",markerDangerAreaScaleA,1.0);
        nh.param<float>("liauto/markerDangerAreaBoundaryPointsSize",markerDangerAreaBoundaryPointsSize,1.0);

        nh.param<int>("liauto/obstaclePointsThreshold",obstaclePointsThreshold,5);
        nh.param<float>("liauto/obstacleMarkerScale",obstacleMarkerScale,3.0);

        nh.param<float>("liauto/obstacleMarkerR",obstacleMarkerR,1.0);
        nh.param<float>("liauto/obstacleMarkerG",obstacleMarkerG,0.0);
        nh.param<float>("liauto/obstacleMarkerB",obstacleMarkerB,0.0);
        nh.param<float>("liauto/obstacleMarkerLifetime",obstacleMarkerLifetime,0.2);
        nh.param<float>("liauto/obstacleMarkerAlpha",obstacleMarkerAlpha,0.5);

        nh.param<float>("liauto/followPointScale",followPointScale,0.2);
        nh.param<float>("liauto/miniDistance",miniDistance,15);
        nh.param<float>("liauto/initial_GPS_lat",initial_GPS_lat,37.528444);
        nh.param<float>("liauto/initial_GPS_lon",initial_GPS_lon,122.0780557);
        nh.param<int>("liauto/relocationRotateGap",relocationRotateGap,4);
        usleep(100);
    }

    /**
     * imu原始测量数据转换到lidar系，加速度、角速度、RPY
    */
    sensor_msgs::Imu imuConverter(const sensor_msgs::Imu& imu_in)
    {
        sensor_msgs::Imu imu_out = imu_in;
        // 加速度，只跟xyz坐标系的旋转有关系
        Eigen::Vector3d acc(imu_in.linear_acceleration.x, imu_in.linear_acceleration.y, imu_in.linear_acceleration.z);
        acc = extRot * acc;
        imu_out.linear_acceleration.x = acc.x();
        imu_out.linear_acceleration.y = acc.y();
        imu_out.linear_acceleration.z = acc.z();
        // 角速度，只跟xyz坐标系的旋转有关系
        Eigen::Vector3d gyr(imu_in.angular_velocity.x, imu_in.angular_velocity.y, imu_in.angular_velocity.z);
        gyr = extRot * gyr;
        imu_out.angular_velocity.x = gyr.x();
        imu_out.angular_velocity.y = gyr.y();
        imu_out.angular_velocity.z = gyr.z();
        // RPY
        Eigen::Quaterniond q_from(imu_in.orientation.w, imu_in.orientation.x, imu_in.orientation.y, imu_in.orientation.z);
        // 为什么是右乘，可以动手画一下看看
        Eigen::Quaterniond q_final = q_from * extQRPY;
        imu_out.orientation.x = q_final.x();
        imu_out.orientation.y = q_final.y();
        imu_out.orientation.z = q_final.z();
        imu_out.orientation.w = q_final.w();

        if (sqrt(q_final.x()*q_final.x() + q_final.y()*q_final.y() + q_final.z()*q_final.z() + q_final.w()*q_final.w()) < 0.1)
        {
            ROS_ERROR("Invalid quaternion, please use a 9-axis IMU!");
            ros::shutdown();
        }

        return imu_out;
    }
};


/**
 * 发布thisCloud，返回thisCloud对应msg格式
*/
sensor_msgs::PointCloud2 publishCloud(ros::Publisher *thisPub, pcl::PointCloud<PointType>::Ptr thisCloud, ros::Time thisStamp, std::string thisFrame)
{
    sensor_msgs::PointCloud2 tempCloud;
    pcl::toROSMsg(*thisCloud, tempCloud);
    tempCloud.header.stamp = thisStamp;
    tempCloud.header.frame_id = thisFrame;
    if (thisPub->getNumSubscribers() != 0)
        thisPub->publish(tempCloud);
    return tempCloud;
}

/**
 * msg时间戳
*/
template<typename T>
double ROS_TIME(T msg)
{
    return msg->header.stamp.toSec();
}

/**
 * 提取imu角速度
*/
template<typename T>
void imuAngular2rosAngular(sensor_msgs::Imu *thisImuMsg, T *angular_x, T *angular_y, T *angular_z)
{
    *angular_x = thisImuMsg->angular_velocity.x;
    *angular_y = thisImuMsg->angular_velocity.y;
    *angular_z = thisImuMsg->angular_velocity.z;
}

/**
 * 提取imu加速度
*/
template<typename T>
void imuAccel2rosAccel(sensor_msgs::Imu *thisImuMsg, T *acc_x, T *acc_y, T *acc_z)
{
    *acc_x = thisImuMsg->linear_acceleration.x;
    *acc_y = thisImuMsg->linear_acceleration.y;
    *acc_z = thisImuMsg->linear_acceleration.z;
}

/**
 * 提取imu姿态角RPY
*/
template<typename T>
void imuRPY2rosRPY(sensor_msgs::Imu *thisImuMsg, T *rosRoll, T *rosPitch, T *rosYaw)
{
    double imuRoll, imuPitch, imuYaw;
    tf::Quaternion orientation;
    tf::quaternionMsgToTF(thisImuMsg->orientation, orientation);
    tf::Matrix3x3(orientation).getRPY(imuRoll, imuPitch, imuYaw);

    *rosRoll = imuRoll;
    *rosPitch = imuPitch;
    *rosYaw = imuYaw;
}

/**
 * 点到坐标系原点距离
*/
float pointDistance(PointType p)
{
    return sqrt(p.x*p.x + p.y*p.y + p.z*p.z);
}

/**
 * 两点之间距离
*/
float pointDistance(PointType p1, PointType p2)
{
    return sqrt((p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y) + (p1.z-p2.z)*(p1.z-p2.z));
}

#endif
