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

#include <unistd.h>
#include <sstream>

#include "eckit/io/MemoryHandle.h"
#include "eckit/io/TCPSocketHandle.h"
#include "eckit/log/Log.h"
#include "eckit/maths/Functions.h"
#include "eckit/net/Port.h"
#include "eckit/net/TCPServer.h"
#include "eckit/net/TCPStream.h"
#include "eckit/runtime/Application.h"
#include "eckit/runtime/Monitor.h"
#include "eckit/thread/Thread.h"
#include "eckit/thread/ThreadControler.h"


using namespace eckit;


class FDBServer;

class FDBConnection : public Thread {

    net::TCPSocket socket_;

    virtual void run();

public:
    FDBConnection(net::TCPSocket& socket) : socket_(socket) {}
};

void FDBConnection::run() {
    Monitor::instance().name("connection");
    Monitor::instance().show(true);

    try {

        Log::status() << "Receiving connection from " << socket_.remoteHost() << ":" << socket_.remotePort()
                      << std::endl;

        net::TCPStream control(socket_);

        for (;;) {

            // 2. recv a request (atm a 'verb' for action)
            std::string verb;
            control >> verb;

            Log::info() << "received verb " << verb << std::endl;

            if (verb == "archive") {

                // 3.1 recv file size
                Length fsize;
                control >> fsize;

                Log::info() << "received file size " << fsize << std::endl;

                // 3.2 send data port
                net::EphemeralTCPServer data_;
                control << data_.localPort();

                // 3.2 accept data connection
                InstantTCPSocketHandle tcp(data_.accept("Waiting for data connection", 60));

                Log::info() << "Accepted connection from " << data_.remoteHost() << ":" << data_.remotePort()
                            << std::endl;

                MemoryHandle mh_(eckit::round(fsize, 4 * 1024));
                Length total = tcp.saveInto(mh_);

                ASSERT(fsize == total);

                continue;
            }

            if (verb == "flush") {
                Log::info() << "Flushing ... " << std::endl;
                continue;
            }

            if (verb == "stop") {
                Log::info() << "Stopping ... " << std::endl;
                break;
            }
        }
    }
    catch (std::exception& e) {
        Log::error() << "** " << e.what() << " Caught in " << Here() << std::endl;
        Log::error() << "** Exception is ignored" << std::endl;
    }
}

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


class FDBServer : public Application {

    virtual void run();

public:
    FDBServer(int argc, char** argv) : Application(argc, argv, "HOME") {}

    virtual ~FDBServer() {}
};

void FDBServer::run() {
    unique();  // there can be only one

    Log::status() << "Starting server" << std::endl;

    net::TCPServer server(net::Port("fdb", 9013));

    for (;;) {
        Log::status() << "-" << std::endl;
        try {
            ThreadControler ct(new FDBConnection(server.accept()));
            ct.start();
        }
        catch (std::exception& e) {
            Log::error() << "** " << e.what() << " Caught in " << Here() << std::endl;
            Log::error() << "** Exception is ignored" << std::endl;
        }
    }
}

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

int main(int argc, char** argv) {
    FDBServer app(argc, argv);
    app.start();
    return 0;
}
