/*
 *  (c) Copyright 2016-2017, 2021 Hewlett Packard Enterprise Development Company LP.
 *
 *  This software is available to you under a choice of one of two
 *  licenses. You may choose to be licensed under the terms of the
 *  GNU Lesser General Public License Version 3, or (at your option)
 *  later with exceptions included below, or under the terms of the
 *  MIT license (Expat) available in COPYING file in the source tree.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  As an exception, the copyright holders of this Library grant you permission
 *  to (i) compile an Application with the Library, and (ii) distribute the
 *  Application containing code generated by the Library and added to the
 *  Application during this compilation process under terms of your choice,
 *  provided you also meet the terms and conditions of the Application license.
 *
 */

#include <assert.h>
#include <vector>
#include <cstdlib> // rand()

#include "cluster/cluster.h"
#include "cluster/config.h"

#include "resource_manager.h"
#include "partition_manager.h"
#include "node_manager.h"
#include "server_manager.h"

namespace radixtree {

Cluster::Cluster()
    : config_(nullptr), rmgr_(nullptr), pmgr_(nullptr), nmgr_(nullptr), smgr_(nullptr) {
}

Cluster::~Cluster() {
}

void Cluster::Init(Config &config) {
    assert(config.IsValid());
    config_ = new Config();
    assert(config_);
    *config_ = config;

    rmgr_ = new ResourceManager(config_);
    assert(rmgr_);
    rmgr_->Init();

    pmgr_ = new PartitionManager(config_);
    assert(pmgr_);
    pmgr_->Init();

    nmgr_ = new NodeManager(config_);
    assert(nmgr_);
    nmgr_->Init();

    smgr_ = new ServerManager(config_);
    assert(smgr_);
    smgr_->Init();
}

void Cluster::Final() {
    if(smgr_) {
        delete smgr_;
        smgr_=nullptr;
    }

    if(nmgr_) {
        delete nmgr_;
        nmgr_=nullptr;
    }

    if(pmgr_) {
        delete pmgr_;
        pmgr_=nullptr;
    }

    if(rmgr_) {
        delete rmgr_;
        rmgr_=nullptr;
    }

    if(config_) {
        delete config_;
        config_=nullptr;
    }
}

Location Cluster::Locate(ResourceName resource, bool random) {
    Resource r(resource.name);
    PartitionID pid = rmgr_->FindPartition(r);

    // pick a node and the correct port
    // TODO, for now the mapping for DYNAMO is like this:
    // pid -> [nid0, nid1, nid2]
    // nid -> [port0, port1, port2]
    NodeID nid;
    size_t nid_idx;
    if(random) {
        // randomly pick a node
        std::vector<NodeID> nids = pmgr_->FindNodes(pid);
        nid_idx = std::rand()%nids.size();
        nid = nids[nid_idx];
    }
    else {
        // pick the "master" node
        std::vector<NodeID> nids = pmgr_->FindNodes(pid);
        nid_idx = 0;
        nid = nids[nid_idx];
    }
    Node n = nmgr_->GetNode(nid);
    ServerID sid = nmgr_->FindServer(nid);
    Server s = smgr_->GetServer(sid);
    size_t port_idx = n.ports.size()>1?nid_idx:0;
    return Location(s.ip, n.ports[port_idx]);
}

std::vector<uint64_t> Cluster::FindPorts(NodeID nid) {
    return nmgr_->GetNode(nid).ports;
}

std::vector<KVS> Cluster::FindPartitions(NodeID nid) {
    return pmgr_->FindPartitionsByNode(nid);
}

void Cluster::Print() {
    config_->Print();
    rmgr_->Print();
    pmgr_->Print();
    nmgr_->Print();
    smgr_->Print();
}

} // namespace radixtree
