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

#ifndef ecflow_base_AbstractServer_HPP
#define ecflow_base_AbstractServer_HPP

#include <atomic>
#include <memory>

#include <boost/date_time/posix_time/posix_time_types.hpp>

#include "ecflow/base/Stats.hpp"
#include "ecflow/base/ZombieCtrl.hpp"
#include "ecflow/core/CheckPt.hpp"
#include "ecflow/core/SState.hpp"
#include "ecflow/core/Str.hpp"

class Defs;

/// This class is provided so that the cmd can issue requests to the server
/// without depending on the server implementation
///
class AbstractServer {
public:
    virtual ~AbstractServer() = default;

    /// Returns an NON empty string if server is ssl enabled.
    /// 0/ "1 : enabled : uses shared ssl certificates";
    /// 0/ "  : enabled : uses server/port specific ssl certificates";
    virtual const std::string& ssl() const { return ecf::Str::EMPTY(); }

    /// returns the current state of the server
    /// The following table shows the effect of state, on server behaviour:
    ///
    ///           User Request    Task Request   Job Scheduling   Check-pointing
    /// RUNNING      yes               yes              yes            yes
    /// SHUTDOWN     yes               yes              no             yes
    /// HALTED       yes               no               no             no
    virtual SState::State state() const = 0;

    /// returns the server host and port number
    virtual std::pair<std::string, std::string> hostPort() const = 0;

    /// returns the defs held by the server. This should always exist. ECFLOW-182
    virtual defs_ptr defs() const = 0;

    /// Update the defs help by the server. This allows multiple suites to loaded
    /// into the server. Note the input defs will be drained of its suites/externs as they
    /// will get transferred to the server. If the server already has suites of the same
    /// name, then an error message is created. This can be overridden with the force option
    virtual void updateDefs(defs_ptr, bool force) = 0;

    /// Remove all suites,externs,client handles, ready for a new start
    virtual void clear_defs() = 0;

    /// Forces the defs file in the server to be written to disk *IF* no args provided.
    /// Otherwise updated mode OR check_pt interval OR check pt alarm
    virtual bool checkPtDefs(ecf::CheckPt::Mode m         = ecf::CheckPt::UNDEFINED,
                             int check_pt_interval        = 0,
                             int check_pt_save_time_alarm = 0) = 0;

    /// Ask the server to restore the  defs from the checkpt file. If that fails the back check pt is tried
    virtual void restore_defs_from_checkpt() = 0;

    /// This function should be called, when the node tree changes state
    virtual void nodeTreeStateChanged() = 0;

    /// returns true if the server allows task communication
    /// Task-->server communication is stopped by halted()
    virtual bool allowTaskCommunication() const = 0;

    /// Stops job scheduling. Check point is enabled. (i,e if we were previously halted)
    /// However any client request can still communicate with the server
    /// Does NOT affect check pointing. Since any request can make changes to node tree
    /// Places server in SHUTDOWN state.
    ///           User Request    Task Request   Job Scheduling   Check-pointing
    /// RUNNING      yes               yes              yes            yes
    /// SHUTDOWN     yes               yes              no             yes
    /// HALTED       yes               no               no             no
    virtual void shutdown() = 0;

    /// Stop job scheduling *AND*  task communication with server. Failed task request are logged
    /// Hence nodes can be stuck in submitted/active states.
    /// Task based command will continue attempting, communication with the server for up to 24hrs.
    ///
    /// When the server is halted, we do *NOT* do any further check pointing
    /// In a typical operational scenario where we have a home, and backup servers.
    /// The checkpoint file is copied to the backup servers periodically (via a task)
    /// hence we want to preserve the state of the last checkpoint. By prevent any state
    /// changes to the node tree.
    ///
    /// Hence halted(), will completely stop the server. Server will only respond
    /// to user requests. (tasks requests are blocked)
    /// Places server in HALTED state.
    ///           User Request    Task Request   Job Scheduling   Check-pointing
    /// RUNNING      yes               yes              yes            yes
    /// SHUTDOWN     yes               yes              no             yes
    /// HALTED       yes               no               no             no
    virtual void halted() = 0;

    /// Start scheduling tasks and respond to all requests. Check pointing is enabled
    /// Places server in RUNNING state.
    ///           User Request    Task Request   Job Scheduling   Check-pointing
    /// RUNNING      yes               yes              yes            yes
    /// SHUTDOWN     yes               yes              no             yes
    /// HALTED       yes               no               no             no
    virtual void restart() = 0;

    /// Ask the server to reload file the hold list of users and their access rights
    /// The white list file is specified by the environment variable ECF_LISTS
    /// This allows/disallows user access on the live server
    /// Return true if file is reloaded ok, else false and error message if:
    /// 	a/ File does not exist
    ///  	b/ File is empty
    ///  	c/ Errors in parsing file
    /// If errors arise the exist user still stay in affect
    virtual bool reloadWhiteListFile(std::string& errorMsg) = 0;

    virtual bool reloadPasswdFile(std::string& errorMsg)       = 0;
    virtual bool reloadCustomPasswdFile(std::string& errorMsg) = 0;

    /// There are several kinds of authentications:
    ///     a/ None
    ///     b/ List mode.   ASCII file based on ECF_LISTS is defined. referred as white list file
    ///     c/ Secure mode. ASCII file based ECF_PASSWD is defined. Referred to as black list file
    //
    /// Returns true if the given user has access to the server, false otherwise
    virtual bool authenticateReadAccess(const std::string& user, bool custom_user, const std::string& passwd) = 0;
    virtual bool authenticateReadAccess(const std::string& user,
                                        bool custom_user,
                                        const std::string& passwd,
                                        const std::string& path)                                              = 0;
    virtual bool authenticateReadAccess(const std::string& user,
                                        bool custom_user,
                                        const std::string& passwd,
                                        const std::vector<std::string>& paths)                                = 0;

    /// Returns true if user has matching write access privileges.
    virtual bool authenticateWriteAccess(const std::string& user)                                        = 0;
    virtual bool authenticateWriteAccess(const std::string& user, const std::string& path)               = 0;
    virtual bool authenticateWriteAccess(const std::string& user, const std::vector<std::string>& paths) = 0;

    /// Shutdown the server and let 'user' has have exclusive lock on it.
    /// If the lock succeeds return true, (This will end up calling the shutdown()
    /// command on the server). If already locked does nothing and return's false
    virtual bool lock(const std::string& user) = 0;

    /// Unlock's the server., and restarts job scheduling
    virtual void unlock() = 0;

    /// Return the user that has exclusive lock, else an empty string
    virtual const std::string& lockedUser() const = 0;

    /// Return Controller for zombies
    ZombieCtrl& zombie_ctrl() { return zombie_ctrl_; }

    /// returns the statistical class
    Stats& stats() { return stats_; }

    /// Call to update the number of request, & returns stats
    Stats& update_stats() {
        stats_.update();
        return stats_;
    }

    /// Update for number of requests per second
    void update_stats(int poll_interval) { stats_.update_stats(poll_interval); }

    // Instead of immediate node tree traversal at the end of child command
    // we use 'increment_job_generation_count' to defer job generation to server
    // The server will check job generation count at poll time.
    // This approach radically reduces the number of times we traverse the node tree
    // and hence improves server throughput.
    void increment_job_generation_count() { job_gen_count_++; }
    void reset_job_generation_count() { job_gen_count_ = 0; }
    int get_job_generation_count() const { return job_gen_count_; }

    /// This job generation is special, in that it will time out, job generation time >= next poll.
    /// This can be called at the end of a *USER* command(force,alter,requeue,etc), hence time_now may be >=
    /// next_poll_time If this is the case, we will defer job generation
    virtual void traverse_node_tree_and_job_generate(const boost::posix_time::ptime& time_now,
                                                     bool user_cmd_context) const = 0;

    /// returns the number of seconds at which we should check time dependencies
    /// this includes evaluating trigger dependencies and submit the corresponding jobs.
    /// This is set at 60 seconds. But will vary for debug/test purposes only.
    /// For Testing the state change queued->submitted->active duration < submitJobsInterval
    /// If this state change happens at the job submission boundary then
    /// time series can get a skew.
    virtual int poll_interval() const = 0;

    /// enable and disable debug output from the server
    virtual void debug_server_on()  = 0;
    virtual void debug_server_off() = 0;
    virtual bool debug() const      = 0;

protected:
    AbstractServer() = default;

private:
    ZombieCtrl zombie_ctrl_;
    Stats stats_;
    std::atomic<int> job_gen_count_{0};
};

#endif /* ecflow_base_AbstractServer_HPP */
