/*
 * (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 "eckit/config/Resource.h"
#include "eckit/log/Bytes.h"
#include "eckit/log/Log.h"
#include "eckit/serialisation/HandleStream.h"

#include "metkit/mars/MarsHandle.h"


const unsigned long startCRC = 0xffffffffL;

eckit::Reanimator<MarsHandle> MarsHandle::reanimator_;

class MarsHandleStream : public eckit::HandleStream {
    MarsHandle& handle_;

public:
    MarsHandleStream(MarsHandle& handle) : eckit::HandleStream(handle), handle_(handle) {
        handle_.streamMode_ = true;
    }

    ~MarsHandleStream() { handle_.streamMode_ = false; }
};

void MarsHandle::encode(eckit::Stream& s) const {
    TCPHandle::encode(s);
    s << clientID_;
    s << doCRC_;
}

MarsHandle::MarsHandle(eckit::Stream& s) :
    TCPHandle(s),
    length_(0),
    total_(0),
    receiving_(false),
    streamMode_(false),
    doCRC_(false),
    crc_(startCRC) {
    s >> clientID_;

    if (s.endObjectFound()) {
        eckit::Log::info() << "Got old metkit without CRC" << std::endl;
        return;
    }

    s >> doCRC_;
    if (doCRC_)
        eckit::Log::info() << "Got new metkit with CRC" << std::endl;
}

MarsHandle::MarsHandle(const std::string& host, int port, unsigned long long clientID) :
    TCPHandle(host, port),
    clientID_(clientID),
    length_(0),
    total_(0),
    receiving_(false),
    streamMode_(false),
    doCRC_(false),
    crc_(startCRC) {}

MarsHandle::~MarsHandle() {}

std::string MarsHandle::title() const {
    std::ostringstream os;

    os << "Client[";

    os << eckit::net::TCPSocket::hostName(host_);

    os << ":" << port_ << "]";
    return os.str();
}

std::string MarsHandle::metricsTag() const {
    std::ostringstream os;

    os << "client@";

    os << eckit::net::TCPSocket::hostName(host_);

    return os.str();
}


eckit::Length MarsHandle::openForRead() {
    static long size = eckit::Resource<long>("archiveSocketBufferSize", 0);

    connection_.bufferSize(size);

    eckit::TCPHandle::openForRead();

    MarsHandleStream s(*this);


    s << clientID_;  // Send info
    s << 'r';        // Send the request
    s >> length_;    // Get the length back

    eckit::Log::status() << "Receiving " << eckit::Bytes(length_) << std::endl;

    total_     = 0;
    receiving_ = true;
    crc_       = startCRC;

    return length_;
}

void MarsHandle::openForWrite(const eckit::Length& length) {
    eckit::TCPHandle::openForWrite(length);

    MarsHandleStream s(*this);

    length_ = length;

    s << clientID_;  // Send info
    s << 'w';
    s << length_;  // Send the length

    eckit::Log::status() << "Sending " << eckit::Bytes(length_) << std::endl;

    total_     = 0;
    receiving_ = false;
}

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

long MarsHandle::read(void* buffer, long length) {
    if (streamMode_)
        return TCPHandle::read(buffer, length);

    long long left = length_ - total_;

    if (left < length) {
        length = left;
        if (length == 0)
            return 0;
    }

    long len = TCPHandle::read(buffer, length);

    if (doCRC_)
        updateCRC(buffer, len);

    total_ += len;

    return len;
}

long MarsHandle::write(const void* buffer, long length) {
    long len = TCPHandle::write(buffer, length);
    total_ += len;
    return len;
}

void MarsHandle::close() {
    bool gotCRC           = false;
    unsigned long version = 0;
    unsigned long crc     = 0;

    if (length_ > 0 && total_ != length_) {
        TCPHandle::close();

        eckit::Log::error() << "Recieved/Sent " << total_ << " bytes instead of " << length_
                            << std::endl;
        if (eckit::Exception::throwing()) {
            eckit::Log::error() << "A expection is already active" << std::endl;
            return;
        }
        throw eckit::ShortFile("Bad total in MarsHandle");
    }

    if (receiving_) {
        crc_ ^= 0xffffffff;

        length_ = 0;

        // Try to read CRC


        try {
            MarsHandleStream s(*this);
            s >> version;

            unsigned long long c;
            s >> c;

            crc = ((unsigned long)(c));

            gotCRC = true;
        }
        catch (std::exception& e) {
            eckit::Log::warning() << "Cannot read crc: " << e.what() << std::endl;
        }
    }


    if (doCRC_ && gotCRC) {
        eckit::Log::info() << "Local CRC " << crc_ << ", remote CRC " << crc << std::endl;
        ASSERT(version == 1);
        if (crc != crc_) {
            {
                FILE* p = popen("mail mab mar", "w");
                if (p) {
                    fprintf(p, "CRC error\n");
                    pclose(p);
                }
            }

            {
                eckit::PathName lock("~/locks/pause_if_crc_error");
                while (lock.exists()) {
                    eckit::Log::status() << "**** CRC ERROR ****" << std::endl;
                    ::sleep(120);
                }
            }

            eckit::TCPHandle::close();

            throw eckit::SeriousBug("Invalide checksum");
        }
    }

    TCPHandle::close();
}

eckit::Length MarsHandle::estimate() {
    return length_;
}


void MarsHandle::updateCRC(void* buffer, long length) {}

const eckit::ClassSpec& MarsHandle::classSpec() {
    static const eckit::ClassSpec _classSpec = {&TCPHandle::classSpec(), "MarsHandle"};
    return _classSpec;
}
