/////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2017     Statoil ASA
//
//  ResInsight is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
//  WARRANTY; without even the implied warranty of MERCHANTABILITY or
//  FITNESS FOR A PARTICULAR PURPOSE.
//
//  See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
//  for more details.
//
/////////////////////////////////////////////////////////////////////////////////

#include <cvfAssert.h>
#include <cvfConfigCore.h>

#include "RiaQDateTimeTools.h"
#include "RiaTimeHistoryCurveResampler.h"
#include "RiaWeightedMeanCalculator.h"

#include <limits>

// QString tostring(const QDateTime& dt)
//{
//    int y = dt.date().year();
//    int m = dt.date().month();
//    int d = dt.date().day();
//
//    int h = dt.time().hour();
//    int mm = dt.time().minute();
//    int s = dt.time().second();
//
//    return QString("%1.%2.%3 %4:%5:%6").arg(y).arg(m).arg(d).arg(h).arg(mm).arg(s);
//}

//--------------------------------------------------------------------------------------------------
/// Internal constants
//--------------------------------------------------------------------------------------------------
#define DOUBLE_INF std::numeric_limits<double>::infinity()

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiaTimeHistoryCurveResampler::RiaTimeHistoryCurveResampler()
{
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiaTimeHistoryCurveResampler::setCurveData( const std::vector<double>& values, const std::vector<time_t>& timeSteps )
{
    if ( values.empty() || timeSteps.empty() )
    {
        return;
    }

    CVF_ASSERT( values.size() == timeSteps.size() );

    clearData();
    m_originalValues = std::make_pair( values, timeSteps );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiaTimeHistoryCurveResampler::resampleAndComputePeriodEndValues( RiaDefines::DateTimePeriod period )
{
    computePeriodEndValues( period );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiaTimeHistoryCurveResampler::resampleAndComputeWeightedMeanValues( RiaDefines::DateTimePeriod period )
{
    computeWeightedMeanValues( period );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const std::vector<time_t>& RiaTimeHistoryCurveResampler::resampledTimeSteps() const
{
    return m_timeSteps;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const std::vector<double>& RiaTimeHistoryCurveResampler::resampledValues() const
{
    return m_values;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<time_t> RiaTimeHistoryCurveResampler::timeStepsFromTimeRange( RiaDefines::DateTimePeriod period, time_t minTime, time_t maxTime )
{
    if ( minTime > maxTime ) return std::vector<time_t>();

    auto firstOriginalTimeStep = RiaQDateTimeTools::fromTime_t( minTime );
    auto lastOriginalTimeStep  = RiaQDateTimeTools::fromTime_t( maxTime );

    auto currTimeStep = firstResampledTimeStep( firstOriginalTimeStep, period );

    std::vector<time_t> timeSteps;
    while ( RiaQDateTimeTools::lessThan( currTimeStep, lastOriginalTimeStep ) )
    {
        timeSteps.push_back( currTimeStep.toSecsSinceEpoch() );
        currTimeStep = RiaQDateTimeTools::addPeriod( currTimeStep, period );
    }
    timeSteps.push_back( currTimeStep.toSecsSinceEpoch() );

    return timeSteps;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiaTimeHistoryCurveResampler::computeWeightedMeanValues( RiaDefines::DateTimePeriod period )
{
    const auto& origTimeSteps = m_originalValues.second;
    const auto& origValues    = m_originalValues.first;

    if ( origValues.empty() || origTimeSteps.empty() )
    {
        return;
    }

    computeResampledTimeSteps( period );

    m_values.reserve( m_timeSteps.size() );

    size_t origDataSize = origValues.size();
    size_t oi           = 0;
    for ( size_t i = 0; i < m_timeSteps.size(); i++ )
    {
        time_t periodStart =
            i > 0 ? m_timeSteps[i - 1]
                  : RiaQDateTimeTools::subtractPeriod( RiaQDateTimeTools::fromTime_t( m_timeSteps[0] ), period ).toSecsSinceEpoch();
        time_t periodEnd    = m_timeSteps[i];
        time_t periodLength = periodEnd - periodStart;

        RiaWeightedMeanCalculator<double> calc;

        while ( true )
        {
            time_t origTimeStep = 0;
            double origValue    = 0.0;

            if ( oi > origDataSize ) break;

            if ( oi < origDataSize )
            {
                origTimeStep = origTimeSteps[oi];
                origValue    = origValues[oi] != DOUBLE_INF ? origValues[oi] : 0.0;
            }
            else
            {
                origTimeStep = periodEnd;
                origValue    = 0.0;
            }

            if ( oi == 0 )
            {
                if ( origTimeStep == m_timeSteps[i] )
                {
                    calc.addValueAndWeight( origValue, periodLength );
                    oi++;
                    break;
                }
                origValue = 0.0;
            }

            time_t startTime = oi > 0 ? std::max( origTimeSteps[oi - 1], periodStart ) : periodStart;
            time_t endTime   = std::min( origTimeStep, periodEnd );

            calc.addValueAndWeight( origValue, endTime - startTime );

            if ( origTimeStep > m_timeSteps[i] ) break;
            if ( origTimeStep == m_timeSteps[i] )
            {
                oi++;
                break;
            }
            oi++;
        }

        m_values.push_back( calc.weightedMean() );
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiaTimeHistoryCurveResampler::computePeriodEndValues( RiaDefines::DateTimePeriod period )
{
    size_t      origDataSize  = m_originalValues.second.size();
    size_t      oi            = 0;
    const auto& origTimeSteps = m_originalValues.second;
    const auto& origValues    = m_originalValues.first;

    if ( origValues.empty() || origTimeSteps.empty() )
    {
        return;
    }

    computeResampledTimeSteps( period );

    m_values.reserve( m_timeSteps.size() );
    for ( size_t i = 0; i < m_timeSteps.size(); i++ )
    {
        while ( oi < origDataSize && origTimeSteps[oi] < m_timeSteps[i] )
            oi++;

        time_t origTimeStep = oi < origDataSize ? origTimeSteps[oi] : m_timeSteps[i];
        double origValue    = oi < origDataSize ? origValues[oi] : origValues[oi - 1];

        double value;
        if ( oi > 0 && origTimeStep >= m_timeSteps[i] )
        {
            value = interpolatedValue( m_timeSteps[i], origTimeSteps[oi - 1], origValues[oi - 1], origTimeStep, origValue );
        }
        else
        {
            value = origValue;
        }

        m_values.push_back( value );
    }
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiaTimeHistoryCurveResampler::clearData()
{
    m_timeSteps.clear();
    m_values.clear();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiaTimeHistoryCurveResampler::computeResampledTimeSteps( RiaDefines::DateTimePeriod period )
{
    CVF_ASSERT( period != RiaDefines::DateTimePeriod::NONE && m_originalValues.second.size() > 0 );

    auto firstOriginalTimeStep = RiaQDateTimeTools::fromTime_t( m_originalValues.second.front() );
    auto lastOriginalTimeStep  = RiaQDateTimeTools::fromTime_t( m_originalValues.second.back() );

    clearData();
    auto currTimeStep = firstResampledTimeStep( firstOriginalTimeStep, period );

    while ( RiaQDateTimeTools::lessThan( currTimeStep, lastOriginalTimeStep ) )
    {
        m_timeSteps.push_back( currTimeStep.toSecsSinceEpoch() );
        currTimeStep = RiaQDateTimeTools::addPeriod( currTimeStep, period );
    }

    // Add last time step
    m_timeSteps.push_back( currTimeStep.toSecsSinceEpoch() );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QDateTime RiaTimeHistoryCurveResampler::firstResampledTimeStep( const QDateTime& firstTimeStep, RiaDefines::DateTimePeriod period )
{
    QDateTime truncatedTime = RiaQDateTimeTools::truncateTime( firstTimeStep, period );

    if ( RiaQDateTimeTools::lessThan( truncatedTime, firstTimeStep ) ) return RiaQDateTimeTools::addPeriod( truncatedTime, period );
    return truncatedTime;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RiaTimeHistoryCurveResampler::interpolatedValue( time_t t, time_t t1, double v1, time_t t2, double v2 )
{
    CVF_ASSERT( t2 >= t1 );

    if ( t <= t1 ) return v1;
    if ( t >= t2 ) return v2;

    return ( v2 - v1 ) * (double)( t - t1 ) / (double)( t2 - t1 ) + v1;
}
