/* timequery.cc
****************************************************************************************************************
****************************************************************************************************************

    Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2020, 2021, 2026 Askar
    Almatov

    This file is part of Trafficmeter.

    Trafficmeter 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.

    Trafficmeter 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 for more details.

    You should have received a copy of the GNU General Public License along with Trafficmeter. If not, see
    <http://www.gnu.org/licenses/>.
*/

#include <algorithm>
#include <cassert>
#include <cerrno>
#include <cstring>
#include <stdexcept>
#include <dirent.h>

#include "timequery.h"
#include "tmdefs.h"

using namespace iclass;
using namespace trafficmeter;

using std::runtime_error;
using std::set;
using std::string;
using std::vector;
using std::literals::string_literals::operator""s;

/**************************************************************************************************************/
TimeQuery::TimeQuery( const string& dirName, int hashLength ) :
    periodt( "-infinity"s, "infinity"s ),
    dirPath_( Path::base + '/' + dirName ),
    hashLength_( hashLength ),
    vlanFilter_( -1 )
{
}

/**************************************************************************************************************/
TimeQuery::~TimeQuery()
{
    for ( const File& file : files_ )
    {
        assert( file.isScanned );
    }
}

/**************************************************************************************************************/
void
TimeQuery::readDirectory( const set<int>& hashes )
{
    time_t  qStart = begin().unixtime();
    time_t  qEnd = end().unixtime();

    if ( qStart >= qEnd )
    {
        return;
    }

    for ( int hash : hashes )
    {
        vector<FileId>  hashFiles;
        Path&&          hashPath( dirPath_.subdir(hash, hashLength_) );
        DIR*            dir = opendir( hashPath.c_str() );
        struct dirent*  entry;

        if ( dir == nullptr )
        {
            throw runtime_error( "directory open error: " + hashPath + " - " + strerror(errno) );
        }

        while ( (entry = readdir(dir)) != nullptr )
        {
            if ( entry->d_type == DT_REG && isdigit(*entry->d_name) )
            {
                char*   tokenEnd;
                time_t  stem = strtoull( entry->d_name, &tokenEnd, 10 );

                if ( stem < qEnd )
                {
                    int     extension = ( *tokenEnd == '.' )? strtoul( tokenEnd + 1, nullptr, 10 ) : 0;

                    hashFiles.push_back( {hash, stem, extension} );
                }
            }
        }

        closedir( dir );
        sort( hashFiles.begin(), hashFiles.end() );

        auto    first = lower_bound( hashFiles.begin(), hashFiles.end(), FileId(0, qStart) );

        while ( first != hashFiles.begin() && filePath(*(first-1)).mtime() >= qStart )
        {
            --first;
        }

        for ( auto i = first; i != hashFiles.end(); ++i )
        {
            files_.push_back( {*i, -1, false, nullptr, 0, 0} );
        }
    }

    sort( files_.begin(), files_.end() );
}

/**************************************************************************************************************/
TimeQuery::Range
TimeQuery::getFiles( time_t timeBound )
{
    TimeQuery::Range    range = { files_.end(), files_.end() };

    for ( auto i = files_.begin(); i != files_.end(); ++i )
    {
        if ( !i->isScanned )
        {
            range.first = i;
            break;
        }
    }

    for ( auto i = range.first; i != files_.end(); ++i )
    {
        if ( i->stem() >= timeBound )
        {
            range.second = i;
            break;
        }
    }

    return range;
}

/**************************************************************************************************************/
bool
TimeQuery::arrangeInterval()
{
    time_t  now = time( nullptr ) - 2;  // 2 seconds pause to complete writing of last records

    if ( begin().isabsolutebegin() )
    {
        if ( end().isabsoluteend() )
        {
            setbegin( moduloRoundDown(now, GROUP_FACTOR) - GROUP_FACTOR );
        }
        else
        {
            setbegin( moduloRoundDown(end().unixtime()-1, GROUP_FACTOR) );
        }
    }

    if ( end().isabsoluteend() )
    {
        time_t  endTime = moduloRoundUp( begin().unixtime(), GROUP_FACTOR );

        if ( now < endTime + GROUP_FACTOR && !Path(PID_FILE).isExists() )
        {
            endTime = now + 2;
        }

        setend( endTime );
    }

    return periodt::duration() > 0.0L;
}

/**************************************************************************************************************/
bool
TimeQuery::setVlanFilter( int vlan )
{
    if ( vlan < 0 || vlan >= 4096 )
    {
        return false;
    }

    vlanFilter_ = vlan;
    return true;
}

/**************************************************************************************************************/
Path
TimeQuery::filePath( FileId file ) const
{
    return std::move( dirPath_.subdir(file.hash(), hashLength_) + '/' + file.fileName() );
}
