#include "FilesStatisticsService.h"
#include <sys/statvfs.h> //for getting disk free space
#include <sys/stat.h>    //for getting files sizes
#include "Time.h"
// --------------------------------------------------------------------------
//
//! Default constructor. Should NOT be used otherwise the service name will be wrong
//
//FilesStatisticsService::FilesStatisticsService()
//{
 //   FilesStatisticsService("YouDidntCallTheProperConstructor");
//}
// --------------------------------------------------------------------------
//
//! Constructor with correct service name. The state machine using this object should give it
//! its own name as a parameter
//! @param serverName the name of the server which created this object
//
FilesStatisticsService::FilesStatisticsService(const string& serverName, MessageImp* mess) : fServerName(serverName),
                                                                                             fCurrentFolder("."),
                                                                                             fContinueStats(true),
                                                                                             fDebug(false),
                                                                                             fBaseSize(0),
                                                                                             fPeriodDuration(1),
                                                                                             fMess(mess)
{
    fStats.sizeWritten = 0;
    fStats.freeSpace = 0;
    fStats.writingRate = 0;
    fService = new DimDescribedService(serverName + "/STATS", "X:3", fStats, "Statistics about size written");
    fThread = boost::thread(boost::bind(&FilesStatisticsService::UpdateService, this));
}
// --------------------------------------------------------------------------
//
//! Default destructor.
//
FilesStatisticsService::~FilesStatisticsService()
{
    fContinueStats = false;
//WARNING: DO NOT DO JOIN OTHERWISE THE DESTRUCTOR BLOCKS
//    fThread.join();
    delete fService;
}
// --------------------------------------------------------------------------
//
//! Retrieves the free space of the current base path
//! @return the available free space on disk, in bytes
//
long FilesStatisticsService::GetFreeSpace()
{
    struct statvfs vfs;
    if (statvfs(fCurrentFolder.c_str(), &vfs))
        return -1;
    return vfs.f_bsize*vfs.f_bavail;
}
// --------------------------------------------------------------------------
//
//! Retrieves the size on disk of a given file, in bytes
//! @param file the filename for which the size should be retrieved
//! @return the size of the file, in bytes
//
long FilesStatisticsService::GetFileSizeOnDisk(const string& file)
{
     errno = 0;
     struct stat st;
     if (!stat(file.c_str(), &st))
         return st.st_size;

     if (errno != 0 && errno != 2)//ignoring error #2: no such file or directory is not an error for new files
     {
         ostringstream str;
         str << "Unable to stat " << file << ". Reason: " << strerror(errno) << " [" << errno << "]";
         fMess->Error(str);
     }

     return -1;
}
// --------------------------------------------------------------------------
//
//! Sets the current folder
//! @param folder the path to the folder
//
bool FilesStatisticsService::SetCurrentFolder(const string& folder)
{
    fMutex.lock();
    fCurrentFolder = folder;

    fStats.freeSpace = GetFreeSpace();
    if (fStats.freeSpace == -1)
    {
        fMess->Error("Could not stat the given folder. Ignoring it");
        fMutex.unlock();
        return false;
    }
    fMutex.unlock();
    return true;
}
// --------------------------------------------------------------------------
//
//! Updates the service. This is the function executed by the thread
//
void FilesStatisticsService::UpdateService()
{
    Time cTime = Time();
    Time previousTime;
    fStats.freeSpace = GetFreeSpace();
    fService->updateService();
    sleep(1);
    while (fContinueStats)
    {
        if (fPeriodDuration == 0.0f)
        {
            sleep(0.1f);
            continue;
        }
        sleep(fPeriodDuration);
        fMutex.lock();
        previousTime = cTime;
        cTime = Time();
        fStats.freeSpace = GetFreeSpace();
        //gather the size of opened files.
        long previousSize = fStats.sizeWritten;
        fStats.sizeWritten = 0;
        for (set<string>::const_iterator it = fOpenedFiles.begin(); it != fOpenedFiles.end(); it++)
            fStats.sizeWritten += GetFileSizeOnDisk(*it);
        fStats.sizeWritten -= fBaseSize;
        long timeElapsed = (cTime - previousTime).total_seconds();
        if (timeElapsed != 0)
            fStats.writingRate = (fStats.sizeWritten - previousSize)/timeElapsed;
        else
            fStats.writingRate = 0;
        fService->updateService();
        fMutex.unlock();
        if (fDebug)
        {
            ostringstream str;
            str << "Size written: " << fStats.sizeWritten/1024 << " kB; writing rate: ";
            str << fStats.writingRate/1024 << " kB/s; free space: ";
            str << fStats.freeSpace/(1024*1024) << " MB";
            fMess->Debug(str);
        }
    }
}
// --------------------------------------------------------------------------
//
//! Let the object know that a new file has been opened
//! @param fileName the full name of the file newly opened
//! @return whether this file could be stated or not
//
bool FilesStatisticsService::FileOpened(const string& fileName)
{
    if (fOpenedFiles.find(fileName) != fOpenedFiles.end())
        return false;
    fMutex.lock();
    //Add a newly opened file, and remember its original size
    long newSize = GetFileSizeOnDisk(fileName);
    if (newSize != -1)
    {
        fBaseSize += newSize;
        fOpenedFiles.insert(fileName);
    }
    else
    {
        fMess->Error("Could not stat file name: " + fileName);
        fMutex.unlock();
        return false;
    }
    fMutex.unlock();
    return true;
}
// --------------------------------------------------------------------------
//
//! Set the debug mode on and off
//! @param debug the new mode (true or false)
//
void FilesStatisticsService::SetDebugMode(bool debug)
{
    if (fDebug == debug)
    {
        fMess->Error("Debug mode already in the asked state");
        return;
    }
    fDebug = debug;
    if (fDebug)
    {
        fMess->Debug("Debug mode is now on");
    }
}
// --------------------------------------------------------------------------
//
//! Set the update of the service interval
//! @param duration the duration between two services update, in second
//
void FilesStatisticsService::SetStatPeriod(const float duration)
{
    if (duration < 0)
    {
        fMess->Error("Statistics period duration should be greater than zero. Discarding");
        return;
    }
    if (!finite(duration))
    {
        fMess->Error("Provided duration does not appear to be a valid float. Discarding");
        return;
    }
    if (duration == fPeriodDuration)
        fMess->Warn("Statistics period not modified. Supplied value already in use");
    if (duration == 0)
        fMess->Message("Statistics are now OFF");
    else
    {
        ostringstream str;
        str << "Statistics period is now " << duration << " seconds";
        fMess->Message(str);
    }
    fPeriodDuration = duration;
}
// --------------------------------------------------------------------------
//
//! Retrieves the latest calculated size written and free disk space
//! @param the FileStatisticsData data structure to be filled in
//
void FilesStatisticsService::GetTotalSizeWritten(FileStatisticsData& data)
{
    data.sizeWritten = fStats.sizeWritten;
    data.writingRate = fStats.writingRate;
    data.freeSpace = fStats.freeSpace;
}
// --------------------------------------------------------------------------
//
//! Resets the files statistics object. Basically sets all counters to zero
//
void FilesStatisticsService::Reset()
{
    fStats.sizeWritten = 0;
    fStats.writingRate = 0;
    fBaseSize = 0;
    fOpenedFiles.clear();
}
