#ifndef INCLUDED_BOBCAT_PROCESS_
#define INCLUDED_BOBCAT_PROCESS_

#include <string>
#include <ostream>
#include <istream>
#include <thread>
#include <mutex>
#include <condition_variable>

#include <bobcat/fork>
#include <bobcat/string>
#include <bobcat/pipe>

#include <bobcat/selector>
#include <bobcat/ifdbuf>
#include <bobcat/ofdbuf>
#include <bobcat/iostream>
#include <bobcat/processenums>
#include <bobcat/signal>
#include <bobcat/exception>

#if defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
    #ifndef BOBCAT_DIY_CLOEXEC_
    #define BOBCAT_DIY_CLOEXEC_
    #endif
#endif

namespace FBB
{

struct Process: private Fork, private SignalHandler,
                public IOStream, public ProcessEnums
{
    friend Process &operator|(Process &lhs, Process &rhs);

    enum ProcessType
    {
        NO_PATH,
        USE_PATH,
        USE_SHELL
    };

    private:
        bool            d_active;
        IOMode          d_mode;
        size_t          d_timeLimit;      // seconds allowed to child-process
        ProcessType     d_processType;

        size_t          d_setMode;          // these values are set by the
        size_t          d_setTimeLimit;   // set members and used as
        ProcessType     d_setProcessType;   // defaults unless overridden by
                                            // actual values
                                            // Constructors set these values
                                            // too.
        std::string     d_command;

        Pipe d_oChildInPipe;    // cin read by the CHILD
        Pipe d_iChildOutPipe;   // cout written by the CHILD
        Pipe d_iChildErrPipe;   // cerr written by the CHILD

        OFdBuf    d_oChildInbuf;      // Child extracts,
        IFdBuf    d_iChildOutbuf;     // Child inserts,
        IFdBuf    d_iChildErrbuf;     // Child inserts

        std::ostream   d_oChildIn;          // Parent inserts to child's cin
        std::istream   d_iChildOut;         // Parent extracts child's cout
        std::istream   d_iChildErr;         // Parent extracts child's cerr

        Selector        d_selector;         // senses activities on Child's
                                            // out/err streams
        struct RetPid
        {
            int     ret;
            pid_t   pid;

            RetPid();                       //  1.f
        };

        RetPid d_child;

        std::thread d_limit;
        volatile ChildAction d_action = CHILD_ENDED;
        std::mutex d_mutex;
        std::condition_variable d_condition;
        size_t d_bufSize;

        int d_exitStatus;

#ifdef BOBCAT_DIY_CLOEXEC_
        int d_closedByChild = 0;        // DIY CLOSE_ON_EXEC
#endif

    public:
        explicit Process(std::string const &command = "");              // 1

        explicit Process(IOMode iomode, std::string const &command = ""); // 2

        Process(IOMode mode, ProcessType type,                            // 3
                                    std::string const &command = "");

        Process(IOMode mode, ProcessType type, size_t timeLimit,          // 4
                                    std::string const &command = "");

        Process(IOMode mode, ProcessType type, size_t timeLimit,          // 5
                 size_t bufSize, std::string const &command = "");

        Process(Process const &other) = delete;

        ~Process() override;                 // stop()s any ongoing process

            Process &operator=(Process const &other) = delete;

        size_t bufSize() const;             // returns current buffer size  .f
        IOMode ioMode() const;              // returns default IOMode       .f
        ProcessType processType() const;    // returns default ProcessType  .f
        size_t timeLimit() const;           // returns default time limit   .f

        void setBufSize(size_t bufSize);    //                              .f
        void setIOMode(IOMode mode);        // change IOMode                .f

        void setProcessType(ProcessType type);                      //  .f

        void setTimeLimit(size_t timeLimit);                        //  .f
                                                // for the next cmd to start
                                                // 0 means: no time monitor

        void setCommand(std::string const &command);    // sets cmd,    .f

        Process &operator+=(std::string const &text);   //       opaddis.f
                                                        // adds to the command

        void start();                                   // 1.f
        void start(IOMode mode);                        // 2.f
        void start(IOMode mode, ProcessType type);      // 3.f

        void start(size_t mode, ProcessType type, size_t timeLimit);    // 1
        void start(IOMode mode, ProcessType type,                       // 2
                    size_t timeLimit, size_t bufSize);


        void system();              // calls /bin/sh -c cmd;            1.f
                                    //      shell redirs OK
        void system(IOMode mode);                                   //  2.f
        void system(IOMode mode, size_t timeLimit);                 //  3.f
        void system(IOMode mode, size_t timeLimit, size_t bufSize); //  4.f


        int operator=(std::string const &cmd);  // sets and starts a command


        std::string const &str() const; // current command              .f

        template <typename Type>
        Process &operator<<(Type const &value);                 // opinsert.f

        template <typename Type>
        Process &operator>>(Type &value);                       // opextract.f

        Process &operator<<(std::ostream &(*pf)(std::ostream &));

        int stop();                     // terminate a running childprocess

        void close();                           // closes input to the child
        int eoi();                              // closes input and waits for
                                                // the child to end
        using Fork::waitForChild;
        int exitStatus() const;                                 //      .f

        bool active();

        Process &operator()(IOMode mode);                       // opfun1.f
        Process &operator()(IOMode mode, ProcessType type);     // opfun2.f

                                                            // operatorfun1.cc
        Process &operator()(IOMode mode, ProcessType type, size_t timeLimit);

        Process &operator()(IOMode mode, ProcessType type,  // operatorfun2.cc
                            size_t timeLimit, size_t bufSize);

        std::istream &childErrStream();         // READ cerr from the child

        std::istream &childOutStream();         //                      .f
                                                // READ cout from the child
                                                // (also: direct through
                                                // extraction)

        size_t available();                     // returns ChildOutput bit_or
                                                // (cf. manpage)

        int waitForChild();

        void showMode(char const *lab) const;

    private:

        void childRedirections() override;
        void childProcess() override;

        void parentRedirections() override;
        void parentProcess() override;

        void signalHandler(size_t signum) override;

        void eoi_() override;

        void newPipe(Pipe &pipe);           // close and reopen pipe.
        void newPipes();                    // close and reopen all necessary
                                            // pipes

        pid_t discontinue(RetPid &proc);
        ChildOutput whichStream();

        void close(int fd);

        void closePipes();

        void rmBackticks();

        void forking();

        IOMode sanitizeIOMode(IOMode mode);
        void closeChildInputOnExec();

        using ConstIter =  std::string::const_iterator;
        struct ExecContext;
        ExecContext analyzeCommand();

        static void limiter(Process *process);
};

inline size_t Process::bufSize() const
{
    return d_bufSize;
}
inline std::istream &Process::childOutStream()
{
    return static_cast<std::istream &>(*this);
}
inline int Process::exitStatus() const
{
    return d_exitStatus;
}
inline Process::IOMode Process::ioMode() const
{
    return d_mode & ~(IN_PIPE | OUT_PIPE | CLOSE_ON_EXEC);
}
inline Process &Process::operator+=(std::string const &command)
{
    d_command += command;
    return *this;
}
template <typename Type>
Process &Process::operator>>(Type &value)
{
    if ((available() & CHILD_COUT) || active())
        dynamic_cast<std::istream &>(*this) >> value;

    return *this;
}
inline Process &Process::operator()(IOMode mode)
{
    return operator()(mode, d_setProcessType, d_setTimeLimit);
}
inline Process &Process::operator()(IOMode mode, ProcessType type)
{
    return operator()(mode & ~(IN_PIPE | OUT_PIPE | CLOSE_ON_EXEC),
                                                    type, d_setTimeLimit);
}
template <typename Type>
Process &Process::operator<<(Type const &value)
{
    if (active())
        dynamic_cast<std::ostream &>(*this) << value;

    return *this;
}
inline Process::ProcessType Process::processType() const
{
    return d_setProcessType;
}
inline void Process::setBufSize(size_t bufSize)
{
    d_bufSize =   bufSize == 0 ? 1 : bufSize;
}
inline void Process::setCommand(std::string const &command)
{
    d_command = command;
}
inline void Process::setIOMode(IOMode mode)
{
    d_setMode = sanitizeIOMode(mode);
}
inline void Process::setProcessType(ProcessType type)
{
    d_setProcessType = d_processType = type;
}
inline void Process::setTimeLimit(size_t timeLimit)
{
    d_setTimeLimit = d_timeLimit = timeLimit;
}
inline void Process::start()
{
    start(d_setMode, d_setProcessType, d_setTimeLimit);
}
inline void Process::start(IOMode mode)
{
    start(mode & ~(IN_PIPE | OUT_PIPE | CLOSE_ON_EXEC),
                                        d_setProcessType, d_setTimeLimit);
}
inline void Process::start(IOMode mode, ProcessType type)
{
    start(mode & ~(IN_PIPE | OUT_PIPE | CLOSE_ON_EXEC), type, d_setTimeLimit);
}
inline std::string const &Process::str() const
{
    return d_command;
}
inline void Process::system()
{
    start(d_mode, USE_SHELL, d_timeLimit, bufSize());
}
inline void Process::system(IOMode mode)
{
    start(mode, USE_SHELL, d_timeLimit, bufSize());
}
inline void Process::system(IOMode mode, size_t timeLimit)
{
    start(mode, USE_SHELL, timeLimit, bufSize());
}
inline void Process::system(IOMode mode, size_t timeLimit, size_t bufSize)
{
    start(mode, USE_SHELL, timeLimit, bufSize);
}
inline size_t Process::timeLimit() const
{
    return d_setTimeLimit;
}


} // FBB

#endif
