/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.client.solrj.cloud.autoscaling;

import java.lang.invoke.MethodHandles;
import java.util.Comparator;
import java.util.List;

import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.common.params.CollectionParams;
import org.apache.solr.common.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.apache.solr.common.params.CollectionParams.CollectionAction.MOVEREPLICA;

public class MoveReplicaSuggester extends Suggester {
  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

  @Override
  SolrRequest init() {
    SolrRequest operation = tryEachNode(true);
    if (operation == null) operation = tryEachNode(false);
    return operation;
  }

  SolrRequest tryEachNode(boolean strict) {
    //iterate through elements and identify the least loaded
    List<Violation> leastSeriousViolation = null;
    Row bestSrcRow = null;
    Row bestTargetRow = null;
    ReplicaInfo sourceReplicaInfo = null;
    List<Pair<ReplicaInfo, Row>> validReplicas = getValidReplicas(true, true, -1);
    validReplicas.sort(leaderLast);
    for (int i1 = 0; i1 < validReplicas.size(); i1++) {
      lastBestDeviation = null;
      Pair<ReplicaInfo, Row> fromReplica = validReplicas.get(i1);
      Row fromRow = fromReplica.second();
      ReplicaInfo ri = fromReplica.first();
      if (ri == null) continue;
      final int i = session.indexOf(fromRow.node);
      int stopAt = force ? 0 : i;
      Row targetRow = null;
      for (int j = session.matrix.size() - 1; j >= stopAt; j--) {
        targetRow = session.matrix.get(j);
        if (targetRow.node.equals(fromRow.node)) continue;
        if (!isNodeSuitableForReplicaAddition(targetRow, fromRow)) continue;
        targetRow = targetRow.addReplica(ri.getCollection(), ri.getShard(), ri.getType(), strict); // add replica to target first
        Row srcRowModified = targetRow.session.getNode(fromRow.node).removeReplica(ri.getCollection(), ri.getShard(), ri.getType());//then remove replica from source node
        List<Violation> errs = testChangedMatrix(strict, srcRowModified.session);
        Policy.Session tmpSession = srcRowModified.session;

        if (!containsNewErrors(errs) &&
            isLessSerious(errs, leastSeriousViolation) &&
            (force || (tmpSession.indexOf(srcRowModified.node) < tmpSession.indexOf(targetRow.node)))) {

          int result = -1;
          if (!force && srcRowModified.isLive && targetRow.isLive)  {
            result = tmpSession.getPolicy().clusterPreferences.get(0).compare(srcRowModified, tmpSession.getNode(targetRow.node), true);
            if (result == 0) result = tmpSession.getPolicy().clusterPreferences.get(0).compare(srcRowModified, tmpSession.getNode(targetRow.node), false);
          }

          if (result <= 0) {
            leastSeriousViolation = errs;
            bestSrcRow = srcRowModified;
            sourceReplicaInfo = ri;
            bestTargetRow = targetRow;
          }
        }
      }
    }
    if (bestSrcRow != null) {
      this.session = bestSrcRow.session;
      return new CollectionAdminRequest.MoveReplica(
          sourceReplicaInfo.getCollection(),
          sourceReplicaInfo.getName(),
          bestTargetRow.node);
    }
    return null;
  }

  static Comparator<Pair<ReplicaInfo, Row>> leaderLast = (r1, r2) -> {
    if (r1.first().isLeader) return 1;
    if (r2.first().isLeader) return -1;
    return 0;
  };


  @Override
  public CollectionParams.CollectionAction getAction() {
    return MOVEREPLICA;
  }
}
