/*
 * Copyright 2009- 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 "ecflow/base/stc/SNewsCmd.hpp"

#include <iostream>

#include "ecflow/base/AbstractServer.hpp"
#include "ecflow/core/Ecf.hpp"
#include "ecflow/core/Log.hpp"
#include "ecflow/node/Defs.hpp"

using namespace std;
using namespace ecf;

/// Custom handling of command logging so that we can add additional debug.
/// Rely's on CSyncCmd not adding newline when logging the command
#define DEBUG_NEWS 1

SNewsCmd::SNewsCmd(unsigned int client_handle,
                   unsigned int client_state_change_no,
                   unsigned int client_modify_change_no,
                   AbstractServer* as) {
    init(client_handle, client_state_change_no, client_modify_change_no, as);
}

/// Called in the server
void SNewsCmd::init(unsigned int client_handle, // a reference to a set of suites used by client
                    unsigned int client_state_change_no,
                    unsigned int client_modify_change_no,
                    AbstractServer* as) {
    news_ = ServerReply::NO_NEWS;

    // =====================================================================================
    // The code to determine changes here must also relate to SSyncCmd
    // ======================================================================================

    if (client_handle == 0) {

        // Here Ecf::modify_change_no() and  Ecf::state_change_no() represent the max change numbers over *all* the
        // suites

        /// *** The client_modify_change_no and client_state_change_no should always be trailing the server
        /// *** i.e the value should be less or equal to server. However if::
        /// ***   o/ Server **dies** we can get the case, where client numbers are greater than server numbers.
        /// ***   o/ Server changes number overflows, since it unsigned, and re-start's with 0
        /// *** When no handle are involved, we can get by with a full sync
        /// *** Note: whenever the server starts, the state and modify numbers start from zero
        if ((client_modify_change_no > Ecf::modify_change_no()) || (client_state_change_no > Ecf::state_change_no())) {

            news_ = ServerReply::DO_FULL_SYNC;

#if DEBUG_NEWS
            std::stringstream ss;
            ss << " [server(" << Ecf::state_change_no() << "," << Ecf::modify_change_no()
               << ") : client no > server no ! :DO_FULL_SYNC]";
            log_append(ss.str());
#else
            log_append("");
#endif
            return;
        }

        if (client_modify_change_no < Ecf::modify_change_no()) {

            news_ = ServerReply::NEWS;

#if DEBUG_NEWS
            std::stringstream ss;
            ss << " [server(" << Ecf::state_change_no() << "," << Ecf::modify_change_no()
               << ") : *Large* scale changes(" << (Ecf::modify_change_no() - client_modify_change_no) << ") :NEWS]";
            log_append(ss.str());
#else
            log_append("");
#endif
            return;
        }

        if (client_state_change_no < Ecf::state_change_no()) {

            news_ = ServerReply::NEWS;

#if DEBUG_NEWS
            std::stringstream ss;
            ss << " [server(" << Ecf::state_change_no() << "," << Ecf::modify_change_no()
               << ") : *Small* scale changes(" << (Ecf::state_change_no() - client_state_change_no) << ") :NEWS]";
            log_append(ss.str());
#else
            log_append("");
#endif
            return;
        }

#if DEBUG_NEWS
        log_append(" [:NO_NEWS]");
#else
        log_append("");
#endif
        return;
    }

    // =============================================================================================
    // Handle used: Determine the max modify and state change no, for suites in our handle
    // =============================================================================================

    /// *** If we can't find the handle, then it may be that the server died ?
    /// *** It is up to the client to do a full sync *including* re-registering suites
    ClientSuiteMgr& client_suite_mgr = as->defs()->client_suite_mgr();
    if (!client_suite_mgr.valid_handle(client_handle)) {

        news_ = ServerReply::DO_FULL_SYNC;

#if DEBUG_NEWS
        std::stringstream ss;
        ss << " [server(" << Ecf::state_change_no() << "," << Ecf::modify_change_no() << ") : Cannot find handle("
           << client_handle << ") :DO_FULL_SYNC]";
        log_append(ss.str());
#else
        log_append("");
#endif

        return;
    }

    /// *** The client_modify_change_no and client_state_change_no should always be trailing the server
    /// *** i.e the value should be less or equal to server. However if
    /// ***   o/ Server **dies** we can get the case, where client numbers are greater than server numbers.
    /// ***   o/ Server changes number overflows, since it unsigned, and re-start's with 0
    /// *** we can get the case, where client numbers are greater than server numbers and also the
    /// *** handle will not exist in the server,
    /// *** It is up to the client to do a full sync *including* re-registering suites
    /// *** Note: whenever the server starts, the state and modify numbers start from zero
    unsigned int max_client_handle_modify_change_no = 0;
    unsigned int max_client_handle_state_change_no  = 0;
    client_suite_mgr.max_change_no(
        client_handle, max_client_handle_state_change_no, max_client_handle_modify_change_no);

    if ((client_modify_change_no > max_client_handle_modify_change_no) ||
        (client_state_change_no > max_client_handle_state_change_no)) {

        news_ = ServerReply::DO_FULL_SYNC;

#if DEBUG_NEWS
        std::stringstream ss;
        ss << " [server handle(" << max_client_handle_state_change_no << "," << max_client_handle_modify_change_no
           << ")  server(" << Ecf::state_change_no() << "," << Ecf::modify_change_no()
           << ") : client no > server no ! :DO_FULL_SYNC]";
        log_append(ss.str());
#else
        log_append("");
#endif

        return;
    }

    /// Changes where user adds a new handle/auto adds/removes require a full update, but only for changed handle
    if (client_suite_mgr.handle_changed(client_handle)) {

        news_ = ServerReply::NEWS;

#if DEBUG_NEWS
        std::stringstream ss;
        ss << " [server handle(" << max_client_handle_state_change_no << "," << max_client_handle_modify_change_no
           << ") server(" << Ecf::state_change_no() << "," << Ecf::modify_change_no()
           << ") : *Large* scale changes (new handle or suites added or removed) :NEWS]";
        log_append(ss.str());
#else
        log_append("");
#endif

        return;
    }

    // The client handle represents a subset of the suites.
    if (client_modify_change_no < max_client_handle_modify_change_no) {

        news_ = ServerReply::NEWS;

#if DEBUG_NEWS
        std::stringstream ss;
        ss << " [server handle(" << max_client_handle_state_change_no << "," << max_client_handle_modify_change_no
           << ") server(" << Ecf::state_change_no() << "," << Ecf::modify_change_no()
           << ") : *Large* scale changes :NEWS]";
        log_append(ss.str());
#else
        log_append("");
#endif

        return;
    }

    // This should also reflect changes made just to the defs(state/suspended) and also the server state
    if (client_state_change_no < max_client_handle_state_change_no) {

        news_ = ServerReply::NEWS;

#if DEBUG_NEWS
        std::stringstream ss;
        ss << " [server handle(" << max_client_handle_state_change_no << "," << max_client_handle_modify_change_no
           << ") server(" << Ecf::state_change_no() << "," << Ecf::modify_change_no()
           << ") : *Small* scale changes :NEWS]";
        log_append(ss.str());
#else
        log_append("");
#endif

        return;
    }

#if DEBUG_NEWS
    log_append(" [:NO_NEWS]");
#else
    log_append("");
#endif
}

/// Called in the client
bool SNewsCmd::handle_server_response(ServerReply& server_reply, Cmd_ptr /*cts_cmd*/, bool debug) const {
    if (debug)
        std::cout << "  SNewsCmd::handle_server_response news_ = " << news_ << "\n";
    server_reply.set_news(news_);
    return true;
}

bool SNewsCmd::equals(ServerToClientCmd* rhs) const {
    auto* the_rhs = dynamic_cast<SNewsCmd*>(rhs);
    if (!the_rhs)
        return false;
    if (news_ != the_rhs->news())
        return false;
    return ServerToClientCmd::equals(rhs);
}

std::string SNewsCmd::print() const {
    std::stringstream ss;
    ss << "cmd:SNewsCmd [ " << news_ << " ] ";
    return ss.str();
}

std::ostream& operator<<(std::ostream& os, const SNewsCmd& c) {
    os << c.print();
    return os;
}
