"""
This is a non-official implementation of 'Client Selection in Federated Learning:
Convergence Analysis and Power-of-Choice Selection Strategies' (https://arxiv.org/abs/2010.01243).
"""
import numpy as np
from flgo.algorithm.fedavg import Client
from flgo.algorithm.fedbase import BasicServer
import flgo.simulator.base as ss

class Server(BasicServer):
    def initialize(self, *args, **kwargs):
        self.init_algo_para({'d': self.num_clients})

    @ss.with_availability
    def sample(self):
        # create candidate set A
        num_candidate = min(self.d, len(self.available_clients))
        p_candidate = np.array([len(self.clients[cid].train_data) for cid in self.available_clients])
        candidate_set = np.random.choice(self.available_clients, num_candidate, p=p_candidate / p_candidate.sum(), replace=False)
        candidate_set = sorted(candidate_set)
        # communicate with the candidates for their local_movielens_recommendation loss
        losses = []
        for cid in candidate_set:
            losses.append(self.clients[cid].test(self.model)['loss'])
        # sort candidate set according to their local_movielens_recommendation loss value, and choose the top-M highest ones
        sort_id = np.array(losses).argsort().tolist()
        sort_id.reverse()
        num_selected = min(self.clients_per_round, len(self.available_clients))
        selected_clients = np.array(self.available_clients)[sort_id][:num_selected]
        return selected_clients.tolist()