#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>

#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();

        typedef std::string::const_iterator ConstIter;
        struct ExecContext;
        ExecContext analyzeCommand();

        static void limiter(Process *process);
};

#include "bufsize.f"
#include "childoutstream.f"
#include "exitstatus.f"
#include "iomode.f"
#include "opaddis.f"
#include "opextract.f"
#include "opfun1.f"
#include "opfun2.f"
#include "opinsert.f"
#include "processtype.f"
#include "setbufsize.f"
#include "setcommand.f"
#include "setiomode.f"
#include "setprocesstype.f"
#include "settimelimit.f"
#include "start1.f"
#include "start2.f"
#include "start3.f"
#include "str.f"
#include "system1.f"
#include "system2.f"
#include "system3.f"
#include "system4.f"
#include "timelimit.f"


} // FBB        

#endif


