/*
 * (C) Copyright 1996- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#ifndef eckit_Pipe_h
#define eckit_Pipe_h

#include "eckit/eckit.h"

#include "eckit/exception/Exceptions.h"
#include "eckit/log/CodeLocation.h"
#include "eckit/memory/NonCopyable.h"
#include "eckit/thread/AutoLock.h"
#include "eckit/thread/Mutex.h"
#include "eckit/thread/Mutex.h"
#include "eckit/thread/MutexCond.h"
#include "eckit/thread/Thread.h"
#include "eckit/thread/ThreadControler.h"
#include "eckit/thread/ThreadControler.h"

namespace eckit {

//----------------------------------------------------------------------------------------------------------------------

template<class PAYLOAD>
class PipeTask;

template<class PAYLOAD>
struct OnePayload;

template<class PAYLOAD>
class Pipe : private NonCopyable {
public:

    typedef void (*Proc)(Pipe<PAYLOAD>&);

    // -- Contructors

    Pipe(long count = 2);

    // -- Destructor

    ~Pipe();

    // -- Methods

    void spawn(Proc proc);

    bool eof();
    void close();

    PAYLOAD& message();
    PAYLOAD& receive();
    void send();

private:

    bool   error();
    void   error(const std::string&);

    // -- Methods

    // -- Members

    Mutex  mutex_;

    long   count_;

    int ri_;
    int wi_;
    int pi_;


    bool   done_;
    bool   error_;
    std::string why_;

    OnePayload<PAYLOAD>* payloads_;
    ThreadControler* controler_;

    // -- Friends

    friend class PipeTask<PAYLOAD>;

};

//-----------------------------------------------------------------------------

template<class PAYLOAD>
struct OnePayload {
    MutexCond cond_;
    bool      ready_;
    bool       done_;
    PAYLOAD   payload_;
    OnePayload(): ready_(false), done_(false), payload_() {}
};

template<class PAYLOAD>
class Pipe;

template<class PAYLOAD>
class PipeTask : public Thread {

    typedef typename Pipe<PAYLOAD>::Proc Proc;

    Pipe<PAYLOAD>&         owner_;
    Proc&        consumer_;
    OnePayload<PAYLOAD>*  payloads_;
public:
    PipeTask(Proc&,Pipe<PAYLOAD>&,OnePayload<PAYLOAD>*);
    virtual void run();
};


template<class PAYLOAD>
Pipe<PAYLOAD>::Pipe(long count):
    count_(count),
    ri_(0),
    wi_(0),
    pi_(-1),
    done_(false),
    error_(false),
    payloads_(0),
    controler_(0)
{

}
template<class PAYLOAD>
Pipe<PAYLOAD>::~Pipe()
{
    close();
}

template<class PAYLOAD>
void Pipe<PAYLOAD>::close()
{
    if(controler_) {
        {
            AutoLock<Mutex> lock(mutex_);
            done_ = true;
        }
        {
            AutoLock<MutexCond> lock(payloads_[wi_].cond_);
            payloads_[wi_].ready_ = true;
            payloads_[wi_].cond_.signal();
        }
        controler_->wait();
    }
    delete[] payloads_;
    controler_ = 0;
    payloads_ = 0;
}


template<class PAYLOAD>
inline void Pipe<PAYLOAD>::error(const std::string& why)
{
    AutoLock<Mutex> lock(mutex_);
    error_ = true;
    why_   = why;
}

template<class PAYLOAD>
inline bool Pipe<PAYLOAD>::error()
{
    AutoLock<Mutex> lock(mutex_);
    return error_;
}


template<class PAYLOAD>
void Pipe<PAYLOAD>::spawn(Proc proc)
{

    payloads_ = new OnePayload<PAYLOAD>[count_];

    error_   = false;

    controler_ = new ThreadControler(new PipeTask<PAYLOAD>(proc, *this, payloads_), false);
    controler_->start();

}

template<class PAYLOAD>
PipeTask<PAYLOAD>::PipeTask(Proc &consumer,
                            Pipe<PAYLOAD>& owner,
                            OnePayload<PAYLOAD>* payloads):
    Thread(false),
    owner_(owner),
    consumer_(consumer),
    payloads_(payloads)
{
}

template<class PAYLOAD>
void PipeTask<PAYLOAD>::run()
{
    try {
        consumer_(owner_);
    }
    catch(std::exception& e)
    {
        Log::error() << "** " << e.what() << " Caught in " <<
                        Here() << std::endl;
        Log::error() << "** Exception is handled" << std::endl;
        owner_.error(e.what());
    }
}

template<class PAYLOAD>
bool Pipe<PAYLOAD>::eof()
{
    AutoLock<Mutex> lock(mutex_);
    return done_;
}

template<class PAYLOAD>
PAYLOAD& Pipe<PAYLOAD>::message()
{
    AutoLock<MutexCond> lock(payloads_[wi_].cond_);

    while(payloads_[wi_].ready_)
        payloads_[wi_].cond_.wait();

    if(error()) {
        AutoLock<Mutex> lck(mutex_);
        done_  = true;
        error_ = false;
        throw SeriousBug(why_);
    }

    return payloads_[wi_].payload_;
}

template<class PAYLOAD>
void Pipe<PAYLOAD>::send()
{
    AutoLock<MutexCond> lock(payloads_[wi_].cond_);

    payloads_[wi_].ready_ = true;
    payloads_[wi_].cond_.signal();

    wi_++;
    wi_ %= count_;
}

template<class PAYLOAD>
PAYLOAD& Pipe<PAYLOAD>::receive()
{

    {
        AutoLock<Mutex> lck(mutex_);
        if(pi_ >= 0) {
            AutoLock<MutexCond> lock(payloads_[pi_].cond_);
            payloads_[pi_].ready_ = false;
            payloads_[pi_].cond_.signal();
        }
    }

    AutoLock<MutexCond> lock(payloads_[ri_].cond_);

    while(!payloads_[ri_].ready_)
        payloads_[ri_].cond_.wait();

    if(error()) {
        AutoLock<Mutex> lck(mutex_);
        throw SeriousBug(why_);
    }

    pi_ = ri_;

    PAYLOAD& p = payloads_[ri_].payload_;
    ri_++;
    ri_ %= count_;

    return p;
}

//----------------------------------------------------------------------------------------------------------------------

} // namespace eckit

#endif
