/*
 * (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.
 */

// File io/MoverHandle.cc
// Baudouin Raoult - (c) ECMWF Jun 11


#include "eckit/io/MoverHandle.h"
#include "eckit/io/MoverTransfer.h"
#include "eckit/io/TCPHandle.h"
#include "eckit/io/cluster/NodeInfo.h"
#include "eckit/net/TCPServer.h"
#include "eckit/runtime/Monitor.h"
#include "eckit/thread/AutoLock.h"
#include "eckit/thread/Thread.h"
#include "eckit/thread/ThreadControler.h"

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

namespace eckit {

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

class MoverHandleThread : public Thread {

    MoverHandle& owner_;
    TCPServer server_;
    TCPHandle local_;
    bool read_;
    bool fail_;
    Mutex mutex_;

public:
    MoverHandleThread(MoverHandle& owner, bool read) :
        owner_(owner),
        local_(NodeInfo::thisNode().host(), server_.localPort()),
        read_(read),
        fail_(false) {}

    void run() {
        {
            AutoLock<Mutex> lock(mutex_);
            if (fail_)
                return;
        }

        try {
            DataHandle& remote = owner_.handle();
            Monitor::instance().name("mover");
            Log::status() << "Using mover" << std::endl;
            MoverTransfer mover;
            if (read_) {
                mover.transfer(remote, local_);
            }
            else {
                mover.transfer(local_, remote);
            }
        }
        catch (TimeOut& e) {
            Log::error() << "** " << e.what() << " Caught in " << Here() << std::endl;
            Log::error() << "** Exception is handled" << std::endl;
            owner_.fail(Retry(e.what()).what());
        }
        catch (std::exception& e) {
            Log::error() << "** " << e.what() << " Caught in " << Here() << std::endl;
            Log::error() << "** Exception is handled" << std::endl;
            owner_.fail(e.what());
        }
    }

    TCPSocket& accept() {

        try {
            return server_.accept("MoverHandle waiting for connection", 60);
        }
        catch (TimeOut& e) {
            AutoLock<Mutex> lock(mutex_);
            fail_ = true;
            Log::error() << "** " << e.what() << " Caught in " << Here() << std::endl;
            Log::error() << "** Exception is handled" << std::endl;
            throw Retry(e.what());
        }
        catch (...) {
            AutoLock<Mutex> lock(mutex_);
            fail_ = true;
            throw;
        }
    }
};

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

MoverHandle::MoverHandle(DataHandle* handle) : handle_(handle), fail_(false) {}

MoverHandle::~MoverHandle() {}

void MoverHandle::fail(const std::string& error) {
    AutoLock<Mutex> lock(mutex_);
    error_ = error;
    fail_  = true;
}

Length MoverHandle::openForRead() {

    MoverHandleThread* t = new MoverHandleThread(*this, true);
    ThreadControler tc(t);
    tc.start();
    data_ = t->accept();
    Log::info() << "Connected" << std::endl;

    return handle_->estimate();
}

void MoverHandle::openForWrite(const Length&) {
    MoverHandleThread* t = new MoverHandleThread(*this, false);
    ThreadControler tc(t);
    tc.start();

    try {
        data_ = t->accept();
    }
    catch (TimeOut& e) {
        // TODO: Check if the thread disapear
        Log::error() << "** " << e.what() << " Caught in " << Here() << std::endl;
        Log::error() << "** Exception is handled" << std::endl;
        throw Retry(e.what());
    }

    Log::info() << "Connected" << std::endl;
}


void MoverHandle::openForAppend(const Length&) {
    NOTIMP;
}

void MoverHandle::close() {
    AutoLock<Mutex> lock(mutex_);
    data_.close();
    if (fail_)
        throw Exception(std::string("MoverHandle::close: ") + error_);
}

long MoverHandle::read(void* buf, long len) {
    AutoLock<Mutex> lock(mutex_);
    if (fail_)
        throw Exception(std::string("MoverHandle::read: ") + error_);
    return data_.read(buf, len);
}

long MoverHandle::write(const void* buf, long len) {
    AutoLock<Mutex> lock(mutex_);
    if (fail_)
        throw Exception(std::string("MoverHandle::write: ") + error_);
    return data_.write(buf, len);
}

void MoverHandle::print(std::ostream& s) const {
    s << "MoverHandle[";
    handle_->print(s);
    s << "]";
}


std::string MoverHandle::title() const {
    return std::string("->") + handle_->title();
}

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

}  // namespace eckit
