/* directory.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 <cctype>
#include <ctime>
#include <iostream>
#include <stdexcept>
#include <libiclass/time>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>

#include "directory.h"
#include "now.h"
#include "tmdefs.h"

using namespace iclass;
using namespace trafficmeter;

using std::cerr;
using std::cout;
using std::endl;
using std::exception;
using std::map;
using std::max;
using std::pair;
using std::string;
using std::vector;

/**************************************************************************************************************/
static bool
create_( const Path& path, int hashLength )
{
    while ( chdir(Path::base.c_str()) != 0 )
    {
        if ( mkdir(Path::base.c_str(), 0777) != 0 )
        {
            return false;
        }
    }

    if ( mkdir((Path::base+'/'+path).c_str(), 0777) != 0 && errno != EEXIST )
    {
        return false;
    }

    if ( hashLength > 0 )
    {
        for ( int hash = 0; hash < (1U << hashLength); ++hash )
        {
            Path&&  hashPath( Path::base + '/' + path.subdir(hash, hashLength) );

            if ( mkdir(hashPath.c_str(), 0777) != 0 && errno != EEXIST )
            {
                return false;
            }
        }
    }

    return true;
}

/**************************************************************************************************************/
Directory::Directory( const string& dirName, int hashLength ) :
    path_( dirName ),
    hashLength_( hashLength ),
    quota_a_( 0 ),
    totalBytes_c_( 0 )
{
    isCreated_ = create_( path_, hashLength );
    createBound_ = time( nullptr ) + 1;
    loader_ = new Loader_;
    loader_->dir_ = this;
}

/**************************************************************************************************************/
Directory::~Directory()
{
    delete loader_;
}

/**************************************************************************************************************/
void
Directory::load()
{
    loader_->start();
}

/**************************************************************************************************************/
void
Directory::loadCancel()
{
    loader_->stop();
}

/**************************************************************************************************************/
void
Directory::setQuota( int quota )
{
    quota_a_ = max( quota, 0 );
}

/**************************************************************************************************************/
Directory::File
Directory::writingFile( int hash, time_t stem, int extension )
{
    return { this, hash, max(stem,createBound_), extension };
}

/**************************************************************************************************************/
vector<Directory::File>
Directory::deletingFiles()
{
    vector<File>  files;

    if ( loader_->finished() && quota_a_ > 0 )
    {
        uintmax_t  bytesQuota = static_cast<uintmax_t>( quota_a_ ) << 30;

        this->lock();

        uintmax_t  dirSize = totalBytes_c_;

        for ( const auto& fs : fileSizes_c_ )
        {
            if ( dirSize < bytesQuota )
            {
                break;
            }

            files.emplace_back( this, fs.first );
            dirSize -= fs.second;
        }

        this->unlock();
    }

    return std::move( files );
}

/**************************************************************************************************************/
bool
Directory::isCreated() const
{
    return isCreated_;
}

/**************************************************************************************************************/
Directory::File::File( Directory* dir, FileId id ) :
    FileId( id ),
    dir_( dir )
{
}

/**************************************************************************************************************/
Directory::File::File( Directory* dir, int hash, time_t stem, int extension ) :
    FileId( hash, stem, extension ),
    dir_( dir )
{
}

/**************************************************************************************************************/
void
Directory::File::removeSize()
{
    dir_->lock();

    auto&&  found( dir_->fileSizes_c_.find(*this) );

    if ( found != dir_->fileSizes_c_.end() )
    {
        dir_->totalBytes_c_ -= found->second;
        dir_->fileSizes_c_.erase( found );
    }

    dir_->unlock();
}

/**************************************************************************************************************/
void
Directory::File::updateSize( uintmax_t newSize )
{
    dir_->lock();

    auto&&  [ row, wasInserted ] = dir_->fileSizes_c_.try_emplace( *this, newSize );

    if ( !wasInserted )
    {
        dir_->totalBytes_c_ -= row->second;
        row->second = newSize;
    }

    dir_->totalBytes_c_ += newSize;
    dir_->unlock();
}

/**************************************************************************************************************/
Path
Directory::File::path() const
{
    return std::move( dir_->path_.subdir(hash(), dir_->hashLength_) + '/' + fileName() );
}

/**************************************************************************************************************/
void
Directory::Loader_::routine()
{
    cout << Now() << "tid=" << tid() << " ./" << dir_->path_ << " loader started" << endl;

    vector<pair<FileId,uintmax_t>>  pairs;

    for ( int hash = 0; !stopped() && hash < (1U << dir_->hashLength_); ++hash )
    {
        Path&&          hashPath( dir_->path_.subdir(hash, dir_->hashLength_) );
        DIR*            dir = opendir( hashPath.c_str() );
        struct dirent*  entry;

        if ( dir == nullptr )
        {
            cerr << "\t./" << dir_->path_ << " loader error: cannot open subdirectory " << hashPath << endl;
            continue;
        }

        while ( !stopped() && (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 );
                int             extension = ( *tokenEnd == '.' )? strtoul( tokenEnd + 1, nullptr, 10 ) : 0;
                File            file( dir_, hash, stem, extension );

                if ( stem < dir_->createBound_ )
                {
                    try
                    {
                        pairs.push_back( {file, file.path().fileSize()} );
                    }
                    catch ( exception& e )
                    {
                        cerr << "\t./" << dir_->path_ << " loader error: " << e.what() << endl;
                    }
                }
            }
        }

        usleep( 1000 );
        closedir( dir );
    }

    sort( pairs.begin(), pairs.end() );

    map<FileId,uintmax_t>   fileSizes;
    uintmax_t               totalBytes = 0;

    for ( auto p : pairs )
    {
        fileSizes.insert( p );
        totalBytes += p.second;
    }

    dir_->lock();

    for ( auto p : dir_->fileSizes_c_ )
    {
        auto&&  [ row, wasInserted ] = fileSizes.insert( p );

        if ( !wasInserted )
        {
            totalBytes -= row->second;
            row->second = p.second;
        }

        totalBytes += p.second;
    }

    dir_->fileSizes_c_ = std::move( fileSizes );
    dir_->totalBytes_c_ = totalBytes;
    dir_->unlock();
    cout << Now() << "tid=" << tid() << " ./" << dir_->path_ << " loader finished" << endl;
}

