package org.apache.helix.task;

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

import java.util.Date;
import java.util.concurrent.TimeUnit;

import org.apache.helix.task.beans.ScheduleBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Configuration for scheduling both one-time and recurring workflows in Helix
 */
public class ScheduleConfig {
  private static final Logger LOG = LoggerFactory.getLogger(ScheduleConfig.class);

  /** Enforce that a workflow can recur at most once per minute */
  private static final long MIN_RECURRENCE_MILLIS = 60 * 1000;

  private final Date _startTime;
  private final TimeUnit _recurUnit;
  private final Long _recurInterval;

  private ScheduleConfig(Date startTime, TimeUnit recurUnit, Long recurInterval) {
    _startTime = startTime;
    _recurUnit = recurUnit;
    _recurInterval = recurInterval;
  }

  /**
   * When the workflow should be started
   * @return Date object representing the start time
   */
  public Date getStartTime() {
    return _startTime;
  }

  /**
   * The unit of the recurrence interval if this is a recurring workflow
   * @return the recurrence interval unit, or null if this workflow is a one-time workflow
   */
  public TimeUnit getRecurrenceUnit() {
    return _recurUnit;
  }

  /**
   * The magnitude of the recurrence interval if this is a recurring task
   * @return the recurrence interval magnitude, or null if this workflow is a one-time workflow
   */
  public Long getRecurrenceInterval() {
    return _recurInterval;
  }

  /**
   * Check if this workflow is recurring
   * @return true if recurring, false if one-time
   */
  public boolean isRecurring() {
    return _recurUnit != null && _recurInterval != null;
  }

  /**
   * Check if the configured schedule is valid given these constraints:
   * <ul>
   * <li>All workflows must have a start time</li>
   * <li>Recurrence unit and interval must both be present if either is present</li>
   * <li>Recurring workflows must have a positive interval magnitude</li>
   * <li>Intervals must be at least one minute</li>
   * </ul>
   * @return true if valid, false if invalid
   */
  public boolean isValid() {
    // All schedules must have a start time even if they are recurring
    if (_startTime == null) {
      LOG.error("All schedules must have a start time!");
      return false;
    }

    // Recurrence properties must both either be present or absent
    if ((_recurUnit == null && _recurInterval != null)
        || (_recurUnit != null && _recurInterval == null)) {
      LOG.error("Recurrence interval and unit must either both be present or both be absent");
      return false;
    }

    // Only positive recurrence intervals are allowed if present
    if (_recurInterval != null && _recurInterval <= 0) {
      LOG.error("Recurrence interval must be positive");
      return false;
    }

    // Enforce minimum interval length
    if (_recurUnit != null) {
      long converted = _recurUnit.toMillis(_recurInterval);
      if (converted < MIN_RECURRENCE_MILLIS) {
        LOG.error("Recurrence must be at least {} ms", MIN_RECURRENCE_MILLIS);
        return false;
      }
    }
    return true;
  }

  /**
   * Create this configuration from a serialized bean
   * @param bean flat configuration of the schedule
   * @return instantiated ScheduleConfig
   */
  public static ScheduleConfig from(ScheduleBean bean) {
    return new ScheduleConfig(bean.startTime, bean.recurUnit, bean.recurInterval);
  }

  /**
   * Create a schedule for a workflow that runs once at a specified time
   * @param startTime the time to start the workflow
   * @return instantiated ScheduleConfig
   */
  public static ScheduleConfig oneTimeDelayedStart(Date startTime) {
    return new ScheduleConfig(startTime, null, null);
  }

  /**
   * Create a schedule for a recurring workflow that should start immediately
   * @param recurUnit the unit of the recurrence interval
   * @param recurInterval the magnitude of the recurrence interval
   * @return instantiated ScheduleConfig
   */
  public static ScheduleConfig recurringFromNow(TimeUnit recurUnit, long recurInterval) {
    return new ScheduleConfig(new Date(), recurUnit, recurInterval);
  }

  /**
   * Create a schedule for a recurring workflow that should start at a specific time
   * @param startTime the time to start the workflow the first time, or null if now
   * @param recurUnit the unit of the recurrence interval
   * @param recurInterval the magnitude of the recurrence interval
   * @return instantiated ScheduleConfig
   */
  public static ScheduleConfig recurringFromDate(Date startTime, TimeUnit recurUnit,
      long recurInterval) {
    if (startTime == null) {
      startTime = new Date();
    }
    return new ScheduleConfig(startTime, recurUnit, recurInterval);
  }
}
