#ifndef PIPEREADER_H
#define PIPEREADER_H

#include "Logger.h"
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Thread.h"

using namespace std;

/*************************************************
The pipe reader is essentially just a thread listening to a fifo
opened in non-blocking mode. And then it writes down whatever it
reads from fifo into a log file.
nickhuan@cisco.com
*************************************************/

class PipeReaderThread: public Thread {
public:
    PipeReaderThread(const string& p_log_name, const string& p_pipe_name){
        m_pipe_name = p_pipe_name;
        m_log_name = p_log_name;
        m_stopRequested = false;
        m_file_fd = -1;
        m_pipe_fd = -1;
    }
    ~PipeReaderThread(){
        if (m_file_fd > 0){
            close(m_file_fd);
        }
        if (m_pipe_fd > 0){
            close(m_pipe_fd);
        }
        if (m_pipe_name.size() > 0){
            unlink(m_pipe_name.c_str());
        }
    }

    bool init(){
        m_file_fd = open(m_log_name.c_str(), O_RDWR|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR);
        if (m_file_fd < 0){
            if (access(m_log_name.c_str(), F_OK) == 0){
                chmod(m_log_name.c_str(), S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
            }else{
                printf("PipeReaderThread::run: failed to open log file %s with %d--%s ", m_log_name.c_str(), errno, strerror(errno));
                return false;
            }
        }
        int s = mkfifo(m_pipe_name.c_str(),0777);
        if (s != 0 && errno != EEXIST) {
            printf("PipeReaderThread::run: failed to mkfifo for pipe %s with %d--%s ", m_pipe_name.c_str(), errno, strerror(errno));
            return false;
        }
        if(chmod(m_pipe_name.c_str(), S_IRWXU|S_IRWXG|S_IRWXO)== -1){
            printf("PipeReaderThread::run failed to chmod fifo %s with %d---%s", m_pipe_name.c_str(), errno, strerror(errno));
            return false;
        }

        while (m_pipe_fd == -1 && !m_stopRequested){
            m_pipe_fd = open(m_pipe_name.c_str(), O_RDONLY|O_NONBLOCK);
            if (m_pipe_fd == -1) {
                if (errno != EAGAIN){
                    printf("PipeReaderThread::run: failed to open pipe %s with %d--%s ", m_pipe_name.c_str(), errno, strerror(errno));
                    return false;
                }else{
                    printf("PipeReaderThread::run: wait for pipe to be opened by other end, now sleep %s with %d--%s ", m_pipe_name.c_str(), errno, strerror(errno));
                }
                sleep(1);
            }
        }
        return true;
    }

    virtual void run(){

        while (!m_stopRequested){
            char buffer[BufferSize+1];
            int sz = read(m_pipe_fd, buffer, BufferSize);
            if (sz > 0) {
                buffer[sz] = '\0';
                LOG_DEBUG(("PipeReaderThread::run: %s", buffer));
                if (write(m_file_fd, buffer, sz) != sz){
                    LOG_ERROR(("PipeReaderThread::run: write file error%d--%s", errno, strerror(errno)));
                    break;
                }
            }
            else {
                if (sz == 0 || sz < 0 && errno == EAGAIN) {
                    LOG_DEBUG(("PipeReaderThread::run: pipe data is not ready for read, sleep..."));
                    usleep(SleepMicroSeconds);
                } else {
                    LOG_ERROR(("PipeReaderThread::run: pipe data read error %d, %s.", errno, strerror(errno)));
                    break;
                }
            }
        }
    }
    void stop(){
        m_stopRequested = true;
        Thread::join();
    }
private:
    int m_file_fd;
    int m_pipe_fd;
    string m_pipe_name, m_log_name;
    bool m_stopRequested;
    static const int BufferSize = 4096;
    static const int SleepMicroSeconds = 50000;

};


class PipeReaderBase{
public:
    PipeReaderBase(){

    }
    ~PipeReaderBase(){
        uninit();
    }
    bool init(const string& p_pipeName){
        m_pipeName = p_pipeName;
        m_logName = p_pipeName + ".log";

        m_PipeReaderThread = new PipeReaderThread(m_logName, m_pipeName);
        if (!m_PipeReaderThread->init()){
            printf("PipeReaderThread failed to init\n");
            return false;
        }
        m_PipeReaderThread->start();

        return true;
    }
protected:
    void uninit(){
        if (m_PipeReaderThread.isNotNull()){
            m_PipeReaderThread->stop();
        }
    }
    RCSmartPtr<PipeReaderThread> m_PipeReaderThread;

    string m_pipeName;
    string m_logName;
};

#endif


