//------------------------------------------------------------------------------
// <copyright file="LqrSMACFileSet.h" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
//   Declarations for the FileSet class, which manages files according to a
//   configurable policy.  In general, data can be appended to the file set and
//   the set of files is constrained according to size.
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------

#ifndef __LQRSMACFILESET_H_
#define __LQRSMACFILESET_H_

#include "LqrSMACByteArray.h"
#include "LqrSMACDeclareException.h"
#include "LqrSMACFileSetSettings.h"
#include "LqrSMACLockableConcept.h"
#include "LqrSMACQueueWorker.h"
#include "LqrSMACSemaphore.h"
#include <fstream>
#include <stdexcept>
#include <string>
#include <boost/shared_ptr.hpp>
#include <boost/signals2.hpp>
#include <boost/thread/locks.hpp>
#include <boost/thread/recursive_mutex.hpp>
#include <boost/utility.hpp>
#include <time.h>

namespace LiquidRobotics {
namespace SMACFramework {

class SHARED_LIB_PUBLIC FileSet : public LockableConceptMutex, boost::noncopyable
{
public:

	typedef boost::shared_ptr<FileSet> Ptr;
    typedef unsigned long FileSizeType;
    typedef std::string string;

    struct FileEntry
    {
        string filePath;
        string fileName;
        FileSizeType fileSize;
        time_t modifiedTime;
    };

    typedef boost::shared_ptr<FileEntry> FileEntryPtr;
    typedef std::list<FileEntryPtr> FileEntryList;
    typedef boost::shared_ptr<FileEntryList> FileEntryListPtr;

    class IUserBlob
    {
    public:
    	virtual ~IUserBlob() { }
    	virtual ByteArrayPtr GetData() = 0;									// called by implementation to get the data to be written
    	virtual void OnWriteComplete(const string& fileName) = 0;			// called by implementation when write has been sent to file
    };

    typedef boost::shared_ptr<IUserBlob> IUserBlobPtr;

	typedef boost::signals2::scoped_connection ScopedConnection;
	typedef boost::shared_ptr<ScopedConnection> ScopedConnectionPtr;
	typedef boost::signals2::signal<void (FileSet& sender, const string& oldFileName)> FileEraseSignalType;
	typedef boost::signals2::signal<void (FileSet& sender, const string& oldFileName, const string& newFileName)> FileRenameSignalType;
	typedef boost::signals2::signal<void (FileSet& sender, const string& oldFileName, string& proposedFileName)> FileVolumeCompleteSignalType;
	typedef boost::signals2::signal<void (FileSet& sender, const string& newFileName, ByteArray& fileInitData)> NewFileSignalType;

    DECLARE_EXCEPTION(AlreadyInitialized, std::runtime_error);
    DECLARE_EXCEPTION(NotInitialized, std::runtime_error);

    // Constructs a FileSet with default settings (not recommended)
    FileSet();

    // Constructs a FileSet using the FileSetSettings instance passed in (makes a copy)
    FileSet(const FileSetSettings& _settings);

    // Destroys a FileSet
    virtual ~FileSet();

    // Gets a copy of the list of file that the FileSet is tracking; the last
    // item in the FileSet is the work file, if one exists (must be initialized)
    FileEntryListPtr GetFileList() const;

    // Returns the path to the work file (the file that is being appended)
    string GetWorkfilePath() const;

    // Gets a reference to the FileSetSettings currently enabled for this
    // instance
    const FileSetSettings& GetSettings() const;

    // Initializes the instance: most methods will throw NotInitialized if Init
    // has not already been called; use Shutdown to close the instance before
    // destroying it
    void Init();

    // Determines whether the instance has been initialized
    bool Initialized() const;

    // Shuts the instance down and returns it to its nominal idle state
    void Shutdown();

    // Writes the array of data to the FileSet as a possibly-atomic set (if
    // KeepFileWritesTogether is set; must be initialized)
    void Write(const ByteArray& data);

    // Writes the string to the FileSet as a possibly-atomic set (if KeepFileWritesTogether
    // is set; must be initialized)
    void Write(const string& data);

    // Writes the string plus a line feed to the FileSet as a possibly-atomic set (if
    // KeepFileWritesTogether is set; must be initialized)
    void WriteLine(const string& data);

    // Writes the blob as specified to the file; useful when firing events for each
    // write for traceability to the file
    void Write(IUserBlobPtr blob);

    // Changes the FileSetSettings to the parameterized value: cannot be set if initialized
    void WriteSettings( const FileSetSettings& newValue);

    // Subscribes to various signals
    ScopedConnectionPtr SubscribeFileEraseSignal(FileEraseSignalType::slot_type slot);
    ScopedConnectionPtr SubscribeFileRenameSignal(FileRenameSignalType::slot_type slot);
    ScopedConnectionPtr SubscribeFileVolumeCompleteSignal(FileVolumeCompleteSignalType::slot_type slot);
    ScopedConnectionPtr SubscribeNewFileSignal(NewFileSignalType::slot_type slot);

    // hold this mutex when unsubscribing to prevent misprocessing during object deletion
    ILockableConcept& GetFiringMutex();

private:

    // this is the data item that is written to the queue
    struct QWDataItem
    {
    	ByteArray bytes;
    	IUserBlobPtr blob;
    };

    typedef boost::shared_ptr<QWDataItem> QWDataItemPtr;

    // Used internally to queue up writes to be executed on another thread
    // (which alleviates the client from needing to wait around for the write
    // to complete before returning); timeouts are used to close the file
    // when there is a gap in writes.
    class QueueWorkerWithTimeout : public QueueWorker<QWDataItemPtr>
    {
    public:

        QueueWorkerWithTimeout();

        int GetTimeout() const;
        void SetTimeout(int newValue);

    protected:

        virtual int DetermineReadTimeout();

    private:

        int readTimeout;
    };

    typedef std::ofstream OFStream;
    typedef boost::shared_ptr<OFStream> OFStreamPtr;
    typedef QueueWorkerWithTimeout QType;
    typedef QueueWorker<string> StringQType;

    void ClearState();
    FileEntryPtr GetFileEntry(const string& pathToFile);
    void MaintainFileEntries();
    OFStreamPtr OpenWorkFile();
    void PeriodicMaintain();
    void PopulateFileEntryList();
    void PostCompression(const string& item);
    void ProcessCompression(StringQType& sender, string item);
    void ProcessWrites(QueueWorker<QWDataItemPtr>& sender, QWDataItemPtr item);
    void ProcessWrites2(const ByteArray& item, Lock& lock);
    static string QuoteName(const string& input, char quote);
    void ReadTimeout(QueueWorkerBase& sender);
    void RollFiles(Lock& lock);
    void ThrowIfInitialized() const;
    void ThrowIfNotInitialized() const;

    bool initialized;												// true if Init was successfully called and not Shutdown
    FileSetSettings settings;										// settings to use in operation
    Semaphore readyToWrite;											// signals when the queue can accept more data
    Semaphore queueEmpty;											// signals when the queue is empty
    FileSetSettings::SizeType numBytesEnqueued;						// the number of bytes enqueued in workItemQueue
    boost::posix_time::ptime lastMaintenanceTime;					// the time when MaintainFileEntries was last called
    QType workItemQueue;											// the queue of ByteArrayPtr containing writes bound for the file
    QType::ScopedConnectionPtr processWritesConnection;				// connection to WorkItemProcessed event from workitemQueue
    QType::ScopedConnectionPtr readTimeoutConnection;				// connection to ReadTimeout event from workItemQueue
    Semaphore compressionSem;										// counting semaphore that controls the number of items in compressionQueue
    StringQType compressionQueue;									// queue of files that need to be compressed
    StringQType::ScopedConnectionPtr processCompressionConnection;	// connection to WorkItemProcessed event from compressionQueue
    FileEntryList feList;											// list of files being watched
    OFStreamPtr workFileStm;										// file stream used to write data
    LockableConceptMutex firingMutex;								// mutex held w
    FileEraseSignalType fileEraseSignal;							// announces when a file has been erased
    FileRenameSignalType fileRenameSignal;							// announces when a file has been renamed
    FileVolumeCompleteSignalType fileVolumeCompleteSignal;			// announces when the work file is full and is going to be renamed, gives an opportunity to scan the file
    																// and propose and alternate name
    NewFileSignalType newFileSignal;								// announces when a new file is created, gives the user an opportunity to write header information
};

}
}

#endif // FILESET_H
