/**
 * 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.hadoop.yarn.server.resourcemanager.reservation.planning;

import java.util.ListIterator;

import org.apache.hadoop.yarn.api.records.ReservationDefinition;
import org.apache.hadoop.yarn.api.records.ReservationRequest;
import org.apache.hadoop.yarn.server.resourcemanager.reservation.Plan;

/**
 * Sets the earliest start time of a stage proportional to the job weight. The
 * interval [jobArrival, stageDeadline) is divided as follows. First, each stage
 * is guaranteed at least its requested duration. Then, the stage receives a
 * fraction of the remaining time. The fraction is calculated as the ratio
 * between the weight (total requested resources) of the stage and the total
 * weight of all proceeding stages.
 */

public class StageEarliestStartByDemand implements StageEarliestStart {

  private long step;

  @Override
  public long setEarliestStartTime(Plan plan,
      ReservationDefinition reservation, int index, ReservationRequest current,
      long stageDeadline) {

    step = plan.getStep();

    // If this is the first stage, don't bother with the computation.
    if (index < 1) {
      return reservation.getArrival();
    }

    // Get iterator
    ListIterator<ReservationRequest> li =
        reservation.getReservationRequests().getReservationResources()
            .listIterator(index);
    ReservationRequest rr;

    // Calculate the total weight & total duration
    double totalWeight = calcWeight(current);
    long totalDuration = getRoundedDuration(current, plan);

    while (li.hasPrevious()) {
      rr = li.previous();
      totalWeight += calcWeight(rr);
      totalDuration += getRoundedDuration(rr, plan);
    }

    // Compute the weight of the current stage as compared to remaining ones
    double ratio = calcWeight(current) / totalWeight;

    // Estimate an early start time, such that:
    // 1. Every stage is guaranteed to receive at least its duration
    // 2. The remainder of the window is divided between stages
    // proportionally to its workload (total memory consumption)
    long window = stageDeadline - reservation.getArrival();
    long windowRemainder = window - totalDuration;
    long earlyStart =
        (long) (stageDeadline - getRoundedDuration(current, plan)
            - (windowRemainder * ratio));

    // Realign if necessary (since we did some arithmetic)
    earlyStart = stepRoundUp(earlyStart, step);

    // Return
    return earlyStart;

  }

  // Weight = total memory consumption of stage
  protected double calcWeight(ReservationRequest stage) {
    return (stage.getDuration() * stage.getCapability().getMemory())
        * (stage.getNumContainers());
  }

  protected long getRoundedDuration(ReservationRequest stage, Plan plan) {
    return stepRoundUp(stage.getDuration(), step);
  }

  protected static long stepRoundDown(long t, long step) {
    return (t / step) * step;
  }

  protected static long stepRoundUp(long t, long step) {
    return ((t + step - 1) / step) * step;
  }
}
