/******************** (C) COPYRIGHT 2020 Geek************************************
* File Name          : simulator_location_pdr.c
* Department         : Sensor Algorithm Team
* Current Version    : V1.2
* Author             : logzhan
* Date of Issued     : 2020.7.4
* Comments           : PDR 输出轨迹平滑的功能函数
********************************************************************************/
/* Header File Including -----------------------------------------------------*/
#include <stdlib.h>
#include <math.h>
#include <float.h>
#include <stddef.h>
#include <string.h>
#include "TrackSmooth.h"
#include "LinearFit.h"
#include "Utils.h"

/* Global Variable Definition ------------------------------------------------*/

/* 轨迹平滑历史输入点存储缓冲区 */
static LatLng_t *SmoothBuffer = NULL; //最新的点在[0]索引处
static size_t SmoothBufferSize = 0;
static size_t elemTrail = 0;
/* 输出点缓存                */
// #define OUTPUT_BUFFER_SIZE 5
static LatLng_t *OutputBuffer = NULL;
static size_t outputTrail = 0;
static size_t outputBufferSize = 0;

/* 拐点存储                  */
static LatLng_t InflectionPoint = {0,0};
/* 直线拟合缓存区 */

static LatLng_t fittingDock[FITTING_DOCK_SIZE] = {{0,0}};
static size_t dockTrail = 0;
/* 条件阈值                  */
const double thresholdDis = 5;                   // 拐点判断阈值
const double thresholdFittingDockSparsity = 1;	 // 直线拟合样本输入点间的稀疏度(即间隙)
const double thresholdFittingDis = 1;            // 拟合直线更新阈值
const double half = 0.5;

/* Function Declaration ------------------------------------------------------*/
/**----------------------------------------------------------------------
* Function    : pushBuffer
* Description : 将输入的位置坐标推入缓存buffer
* Date        : 2020/7/4 logzhan
*---------------------------------------------------------------------**/
static void pushBuffer(LatLng_t inLoc){
	size_t i;
    for (i = outputBufferSize - 1; i > 0; --i){
        OutputBuffer[i] = OutputBuffer[i-1];
    }
    OutputBuffer[0] = inLoc;
    if(outputTrail < outputBufferSize)
        ++outputTrail;
}

/**----------------------------------------------------------------------
* Function    : trackSmoothingSlideWindowSet
* Description : 使用平滑模块时首先调用的函数，设定轨迹平滑窗口大小。也可以通过此函
*               数重新设定滑动窗口大小。
* Input       : slideWindowSize: 滑动窗口的大小
* Output      : size : 当前滑动窗口的大小，一般大于0,当返回大小为0时说明设置滑动
 *              窗口为0或是内存申请失败。
* Date        : 2020/7/4 logzhan
*---------------------------------------------------------------------**/
int TrackSmoothSetWinSize(size_t slideWindowSize)
{
    LatLng_t *ptr = NULL;
	size_t smoothBufferMallocRes = 0;
	size_t OutputBufferMallocRes = 0;

    ptr = (LatLng_t *)malloc( sizeof(LatLng_t) * slideWindowSize);
    if (ptr != NULL)
    {
        smoothBufferMallocRes = slideWindowSize;
        if (SmoothBuffer != NULL)
        {
            free(SmoothBuffer);
            SmoothBuffer = NULL;
            SmoothBufferSize = 0; 
        } 
        SmoothBuffer = ptr;
        SmoothBufferSize = slideWindowSize;
        memset(SmoothBuffer, 0, sizeof(LatLng_t)*SmoothBufferSize);
        elemTrail = 0;
    }
    else
    {
        smoothBufferMallocRes = 0;
    }
    ptr = NULL;
    ptr = (LatLng_t*)malloc( sizeof(LatLng_t) * slideWindowSize);
    if( ptr != NULL)
    {
        OutputBufferMallocRes = slideWindowSize;
        if(OutputBuffer != NULL)
        {
            free(OutputBuffer);
            OutputBuffer = NULL;
            outputBufferSize = 0;
        }
        OutputBuffer = ptr;
        outputBufferSize = slideWindowSize;
        memset(OutputBuffer, 0, sizeof(LatLng_t)*outputBufferSize);
        outputTrail = 0;
    }
    else
    {
        OutputBufferMallocRes = 0;
    }
    /* 不论是否重新调整SmoothBuffer和SmoothBufferSize，都会重置fittingDock和dockTrail*/
    memset(fittingDock, 0, sizeof(LatLng_t)*FITTING_DOCK_SIZE);
    dockTrail = 0;

    return (int)((smoothBufferMallocRes && OutputBufferMallocRes)? slideWindowSize: 0);
}
/**----------------------------------------------------------------------
* Function    : trackSmoothing
* Description : 轨迹平滑模块关闭后需要使用此函数释放资源
* Input       : lat: 输入纬度
*               lon: 输入经度
* Output      : outLat: 输出纬度存储变量指针
*               outLon: 输出经度存储变量指针
*               return: 返回处理状态
* Date        : 2020/7/4 logzhan
*---------------------------------------------------------------------**/
int pdr_trackSmooth(double lat, double lon, double *outLat, double *outLon)
{
	size_t i;
	double distance;
	LatLng_t outLatLng;
    LatLng_t projPointLatLng;
	double fittingDockLat[FITTING_DOCK_SIZE] = { 0 };
	double fittingDockLon[FITTING_DOCK_SIZE] = { 0 };
    LatLng_t tmp_lla = { 0 };
	double a = 0; double b = 0;    /* y = a*x + b */
	// double sumLat = 0; double sumLon = 0;
	int ret;
    LatLng_t fittingProjPoint;
	double distanceFitting;
    if( SmoothBuffer == NULL || SmoothBufferSize <= 1)
    {
        *outLat = lat;
        *outLon = lon;
        return 1;
    }
    /* 配置各个缓冲区、列表的相应内容和参数*/
    /* 将SmoothBuffer右移以插入新数据 */
    for (i = SmoothBufferSize -1 ; i > 0 ; --i){
        SmoothBuffer[i] = SmoothBuffer[i-1];
    }
    SmoothBuffer[0].lat = lat;
    SmoothBuffer[0].lon = lon;
    if (elemTrail == 0) { /* 当第一个点输入时，将其设置为第一个拐点 */
        InflectionPoint.lat = lat;
        InflectionPoint.lon = lon;
    }
    /* 及时更新elemTrail */
    if (elemTrail < SmoothBufferSize ) {
        ++elemTrail;
    }
    /* 初始化用于集合的存储点数组fittingDock[FITTING_DOCK_SIZE]*/
    if (dockTrail < FITTING_DOCK_SIZE) {
		if (dockTrail > 0) 
		{	/*当不是fittingDock的首个元素时，要注意fittingDock输入样本的稀疏度*/
			tmp_lla.lat = lat;
			tmp_lla.lon = lon;
			distance = CalDistance(tmp_lla, fittingDock[dockTrail - 1]);
			if (distance >= thresholdFittingDockSparsity) {
				fittingDock[dockTrail].lat = lat;
				fittingDock[dockTrail].lon = lon;
				if (dockTrail < outputBufferSize)
					++dockTrail;
			}
		}
		else
		{	/*当本次输入的坐标点是fittingDock的首个元素，直接将此坐标点作为拟合直线的起点*/
			fittingDock[dockTrail].lat = lat;
			fittingDock[dockTrail].lon = lon;
			if (dockTrail < outputBufferSize)
				++dockTrail;
		}
    }
    /* 平滑处理逻辑开始 */
    if(elemTrail < 3 ) //计算拐点的前提是输入点至少有3个
    {
        *outLat = lat;
        *outLon = lon;
		outLatLng.lat = lat;
		outLatLng.lon = lon;
        pushBuffer(outLatLng);

    }
    else
    {
        projPointLatLng = ProjPointOfLatLng(SmoothBuffer[1], InflectionPoint, SmoothBuffer[0]);
		distance = CalDistance(SmoothBuffer[1], projPointLatLng);
        /*判断SmoothBuffer[1]到预测直线的距离distance，根据距离的大小执行不同的逻辑*/
        if( distance > thresholdDis)
        {
			/*输入坐标点被判断为拐点后，要更新拐点，清空拟合点集fittingDock*/
            /* 输出点(lat, lon) */
			outLatLng.lat = SmoothBuffer[1].lat;
			outLatLng.lon = SmoothBuffer[1].lon;
            pushBuffer(outLatLng);            
            /* 将(lat, lon)作为新的拐点更新 InflectionPoint */
            InflectionPoint.lat = lat;
            InflectionPoint.lon = lon;
            /* 拐点更新后，重新设置fittingDock中的数值内容 */
            memset(fittingDock, 0, sizeof(LatLng_t) * FITTING_DOCK_SIZE);
            fittingDock[0].lat = lat;
            fittingDock[0].lon = lon;
            dockTrail = 1;
        }
        else
        {
			/*当输入点被判断为非拐点后，根据拟合点集fittingDock的大小执行不同的逻辑*/
            if( dockTrail >= FITTING_DOCK_SIZE)
            {
                
                for ( i = 0 ; i < FITTING_DOCK_SIZE ; ++i)
                {
                    fittingDockLat[i] = fittingDock[i].lat;
                    fittingDockLon[i] = fittingDock[i].lon;
                }
                ret = linearLeastSquaresFit(fittingDockLat, fittingDockLon, FITTING_DOCK_SIZE, &a, &b);
                fittingProjPoint = SmoothBuffer[1];
                if (ret == 0) /* ret==0表示直线拟合成功 */
                {
                    fittingProjPoint.lat = (a*(SmoothBuffer[1].lon - b) + SmoothBuffer[1].lat) / (pow(a, 2) + 1);
                    fittingProjPoint.lon = a*fittingProjPoint.lat + b;
                }
				distanceFitting = CalDistance(SmoothBuffer[1], fittingProjPoint);
                if( distanceFitting > thresholdFittingDis)
                {
					// //FIXME: 射线拟合可能有逻辑漏洞，修复。
                    // dockTrail = 2;
                    // fittingDock[1] = fittingProjPoint;
					// //fittingDock[1] = SmoothBuffer[1]; //TEST
                    // LatLng outLatLng = {lat, lon}; //有问题，漏更一个点
                    // pushBuffer(outLatLng);
                    //TODO: 测试新的添点策略
                    dockTrail           = 2;     /* remove the elements whose the index >= 3 */
                    fittingDock[1] = SmoothBuffer[1];
					outLatLng.lat = SmoothBuffer[1].lat;
					outLatLng.lon = SmoothBuffer[1].lon;
                    pushBuffer(outLatLng);
                }
                else
                {
					outLatLng.lat = fittingProjPoint.lat;
					outLatLng.lon = fittingProjPoint.lon;
                    pushBuffer(outLatLng);
                }
			}else{
				/*当fittingDock未被装满，则说明当前输入点不符合fittingDock的输入条件，直接将其输入以保证实时性*/
				outLatLng.lat = lat;
				outLatLng.lon = lon;
				pushBuffer(outLatLng);
			}
        }

		/* 2019-05-22 平滑改动：由均值滤波变成加权求和*/
		double factor = 0;
		if (outputTrail > 1)
		{
			factor = half / (outputTrail - 1);
		}
		double sumLat = 0; double sumLon = 0;
		for (i = 0; i < outputTrail; ++i)
		{
			sumLat += OutputBuffer[i].lat * factor;
			sumLon += OutputBuffer[i].lon * factor;
		}
        *outLat = sumLat + OutputBuffer[0].lat * (half - factor);
        *outLon = sumLon + OutputBuffer[0].lon * (half - factor);
    }
	return 0;
}

/**----------------------------------------------------------------------
* Function    : freeTrackSmooth
* Description : 轨迹平滑模块关闭后需要使用此函数释放资源
* Date        : 2020/7/4 logzhan
*---------------------------------------------------------------------**/
int freeTrackSmooth(void)
{
    if ( SmoothBuffer != NULL)
    {
        free(SmoothBuffer);
        SmoothBuffer = NULL;
        SmoothBufferSize = 0;
        elemTrail = 0;
    }
    if ( OutputBuffer != NULL)
    {
        free(OutputBuffer);
        OutputBuffer = NULL;
        outputBufferSize = 0;
        outputTrail = 0;
    }
    return 0;
}




