/**
    UNIX shell-like I/O for C++
    $Id$
*/
#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <functional>

namespace shell {
    class cmd {
    public:
        typedef int (*basic_cmd_fn)(const std::vector<std::string>&, std::istream&, std::ostream&);
    private:
        bool done_;
    public:
        cmd() : done_(false) {}
        virtual ~cmd() {}
        void destroy() {
            if (!done_)
                run();
            destroy_impl();
        }
        int run() {
            done_ = true;
            return run_impl();
        }
        virtual int run_impl() =0;
        virtual void destroy_impl() {}
        virtual void set_istream(std::istream*, bool =false) =0;
        virtual void set_ostream(std::ostream*, bool =false) =0;
    };

    class basic_cmd : public cmd {
    private:
        basic_cmd_fn fptr_; // C++ function representing the command
        std::vector<std::string> arg_;
        std::istream* in_;
        std::ostream* out_;
        bool destruct_in_, destruct_out_; // be true if the in_/out_ should be destructed by the object
    public:
        basic_cmd(basic_cmd_fn fptr, const std::vector<std::string>& arg)
            : cmd(), fptr_(fptr), arg_(arg), in_(&std::cin), out_(&std::cout),
              destruct_in_(false), destruct_out_(false) {}

        virtual ~basic_cmd() { destroy(); }
        void destroy_impl() {
            if (destruct_in_) delete in_;
            if (destruct_out_) delete out_;
        }
        int run_impl() {
            return fptr_(arg_, *in_, *out_);
        }
        void set_istream(std::istream* in, bool destruct =false) {
            in_ = in;
            destruct_in_ = destruct;
        }
        void set_ostream(std::ostream* out, bool destruct =false) {
            out_ = out;
            destruct_out_ = destruct;
        }
    };

    // smart_ptr/auto_ptr-like command holder
    class cmd_holder {
    private:
        cmd* p_;
        int* refc_; // reference count
        cmd_holder& operator=(const cmd_holder&); // purposely not implemented (MUST NOT COPY)
    public:
        cmd_holder(cmd* p) : p_(p), refc_(new int(1)) {}
        cmd_holder(const cmd_holder& c) : p_(c.p_), refc_(c.refc_) { (*refc_)++; }
        ~cmd_holder() {
            if (--(*refc_) == 0) {
                delete p_;
                delete refc_;
            }
        }
        cmd* operator->() const { return p_; }
    };
    
    cmd_holder operator<(cmd_holder lhs, const char* filename) {
        lhs->set_istream(new std::ifstream(filename), true);
        return lhs;
    }
    cmd_holder operator>(cmd_holder lhs, const char* filename) {
        lhs->set_ostream(new std::ofstream(filename), true);
        return lhs;
    }
    cmd_holder operator>>(cmd_holder lhs, const char* filename) {
        lhs->set_ostream(new std::ofstream(filename, std::ios_base::out | std::ios_base::app), true);
        return lhs;
    }

    // (command1 | command2) as an abstract command
    class piped_cmd : public cmd {
    private:
        cmd_holder lhs_, rhs_;
    public:
        piped_cmd(cmd_holder& lhs, cmd_holder& rhs) : cmd(), lhs_(lhs), rhs_(rhs) {}
        virtual ~piped_cmd() { destroy(); }

        int run_impl() {
            std::stringstream s; // buffer for communications
            lhs_->set_ostream(&s);
            rhs_->set_istream(&s);
            int ret1 = lhs_->run();
            int ret2 = rhs_->run();
            return ret1 | ret2;
        }
        void set_istream(std::istream* in, bool =false) {
            lhs_->set_istream(in);
        }
        void set_ostream(std::ostream* out, bool =false) {
            rhs_->set_ostream(out);
        }
    };

    // operator overloading
    cmd_holder operator|(cmd_holder lhs, cmd_holder rhs) {
        return cmd_holder( new piped_cmd(lhs, rhs) );
    }

    // for enabling destructor-call
    class basic_cmd_gen
    {
    private:
        basic_cmd::basic_cmd_fn fptr_;
    public:
        basic_cmd_gen(basic_cmd::basic_cmd_fn fptr) : fptr_(fptr) {}
        // hack for omitting ()
        operator cmd_holder() const { return (*this)(); }
        // TODO: >3-arguments
        cmd_holder operator() (const std::vector<std::string>& arg) const {
            return cmd_holder( new basic_cmd(fptr_, arg) );
        }
        cmd_holder operator()() const {
            std::vector<std::string> v;
            return (*this)(v);
        }
        cmd_holder operator()(const std::string& arg1) const {
            std::vector<std::string> v; v.push_back(arg1);
            return (*this)(v);
        }
        cmd_holder operator()(const std::string& arg1, const std::string& arg2) const {
            std::vector<std::string> v; v.push_back(arg1); v.push_back(arg2);
            return (*this)(v);
        }
        cmd_holder operator()(const std::string& arg1, const std::string& arg2, const std::string& arg3) const {
            std::vector<std::string> v; v.push_back(arg1); v.push_back(arg2); v.push_back(arg3);
            return (*this)(v);
        }
    };

    // for legacy compilers (e.g. old cl/bcc32?)
    cmd_holder operator< (const basic_cmd_gen& lhs, const char* filename)      { return lhs() <  filename; }
    cmd_holder operator> (const basic_cmd_gen& lhs, const char* filename)      { return lhs() >  filename; }
    cmd_holder operator>>(const basic_cmd_gen& lhs, const char* filename)      { return lhs() >> filename; }
    cmd_holder operator| (const basic_cmd_gen& lhs,       cmd_holder rhs)      { return lhs() | rhs;       }
    cmd_holder operator| (const basic_cmd_gen& lhs, const basic_cmd_gen& rhs)  { return lhs() | rhs();     }
    cmd_holder operator| (          cmd_holder lhs, const basic_cmd_gen& rhs)  { return lhs   | rhs();     }
    

    // UNIX cat
    int cat_impl(const std::vector<std::string>& arg, std::istream& in, std::ostream& out) {
        char c;
        if (arg.size() > 0) {
            for (std::vector<std::string>::const_iterator it = arg.begin(); it != arg.end(); it++) {
                std::ifstream file_in(it->c_str());
                while (file_in.get(c))
                    out.put(c);
            }
        }
        else {
            while (in.get(c))
                out.put(c);
        }
        return 0;
    }

    // UNIX sort
    // input: accept only stdin, available options: only -r
    int sort_impl(const std::vector<std::string>& arg, std::istream& in, std::ostream& out) {
        std::string s;
        std::vector<std::string> v;
        while (std::getline(in, s, '\n'))
            v.push_back(s);

        if (arg.size() > 0 && arg[0] == "-r") // reverse
            std::sort(v.begin(), v.end(), std::greater<std::string>());
        else
            std::sort(v.begin(), v.end());

        for (std::vector<std::string>::const_iterator it = v.begin(); it != v.end(); it++)
            out << (*it) << '\n';
        return 0;
    }

    //////////////////////////

    // UNIX uniq
    // input: accept only stdin
    int uniq_impl(const std::vector<std::string>&, std::istream& in, std::ostream& out) {
        typedef std::vector<std::string>::const_iterator iterator;
        std::string s;
        std::vector<std::string> v;
        while (std::getline(in, s, '\n'))
            v.push_back(s);
        iterator it_end = std::unique(v.begin(), v.end()); 
        for (iterator it = v.begin(); it != it_end; it++)
            out << (*it) << '\n';
        return 0;
    }

}

// importing for global namespace
static shell::basic_cmd_gen  cat(&shell::cat_impl);
static shell::basic_cmd_gen sort(&shell::sort_impl);
static shell::basic_cmd_gen uniq(&shell::uniq_impl);
