/*
 * 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.ambari.server.actionmanager;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.controller.ExecuteActionRequest;
import org.apache.ambari.server.controller.internal.RequestOperationLevel;
import org.apache.ambari.server.controller.internal.RequestResourceFilter;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.orm.entities.RequestEntity;
import org.apache.ambari.server.orm.entities.RequestOperationLevelEntity;
import org.apache.ambari.server.orm.entities.RequestResourceFilterEntity;
import org.apache.ambari.server.orm.entities.StageEntity;
import org.apache.ambari.server.state.Clusters;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;

public class Request {
  private static final Logger LOG = LoggerFactory.getLogger(Request.class);

  private final long requestId;
  private long clusterId;
  private String clusterName;
  private Long requestScheduleId;
  private String commandName;
  private String requestContext;
  private long createTime;
  private long startTime;
  private long endTime;

  /**
   * If true, this request can not be executed in parallel with any another
   * requests. That is useful when updating MM state, performing
   * decommission etc.
   * Defaults to false.
   */
   private boolean exclusive;

  /**
   * As of now, this field is not used. Request status is
   * calculated at RequestResourceProvider on the fly.
   */
  private HostRoleStatus status; // not persisted yet
  private String inputs;
  private List<RequestResourceFilter> resourceFilters;
  private RequestOperationLevel operationLevel;
  private RequestType requestType;

  private Collection<Stage> stages = new ArrayList<Stage>();

  @AssistedInject
  /**
   * Construct new entity
   */
  public Request(@Assisted long requestId, @Assisted("clusterId") Long clusterId, Clusters clusters) {
    this.requestId = requestId;
    this.clusterId = clusterId.longValue();
    this.createTime = System.currentTimeMillis();
    this.startTime = -1;
    this.endTime = -1;
    this.exclusive = false;

    if (-1L != this.clusterId) {
      try {
        this.clusterName = clusters.getClusterById(this.clusterId).getClusterName();
      } catch (AmbariException e) {
        LOG.debug("Could not load cluster with id {}, the cluster may have been removed for request {}",
            clusterId, Long.valueOf(requestId));
      }
    }
  }

  @AssistedInject
  /**
   * Construct new entity from stages provided
   */
  //TODO remove when not needed
  public Request(@Assisted Collection<Stage> stages, Clusters clusters){
    if (stages != null && !stages.isEmpty()) {
      this.stages.addAll(stages);
      Stage stage = stages.iterator().next();
      this.requestId = stage.getRequestId();
      this.clusterName = stage.getClusterName();
      try {
        this.clusterId = clusters.getCluster(clusterName).getClusterId();
      } catch (Exception e) {
        if (null != clusterName) {
          String message = String.format("Cluster %s not found", clusterName);
          LOG.error(message);
          throw new RuntimeException(message);
        }
      }
      this.requestContext = stages.iterator().next().getRequestContext();
      this.createTime = System.currentTimeMillis();
      this.startTime = -1;
      this.endTime = -1;
      this.requestType = RequestType.INTERNAL_REQUEST;
      this.exclusive = false;
    } else {
      String message = "Attempted to construct request from empty stage collection";
      LOG.error(message);
      throw new RuntimeException(message);
    }
  }

  @AssistedInject
  /**
   * Construct new entity from stages provided
   */
  //TODO remove when not needed
  public Request(@Assisted Collection<Stage> stages, @Assisted ExecuteActionRequest actionRequest,
                 Clusters clusters, Gson gson) throws AmbariException {
    this(stages, clusters);
    if (actionRequest != null) {
      this.resourceFilters = actionRequest.getResourceFilters();
      this.operationLevel = actionRequest.getOperationLevel();
      this.inputs = gson.toJson(actionRequest.getParameters());
      this.requestType = actionRequest.isCommand() ? RequestType.COMMAND : RequestType.ACTION;
      this.commandName = actionRequest.isCommand() ? actionRequest.getCommandName() : actionRequest.getActionName();
      this.exclusive = actionRequest.isExclusive();
    }
  }

  @AssistedInject
  /**
   * Load existing request from database
   */
  public Request(@Assisted RequestEntity entity, StageFactory stageFactory, Clusters clusters){
    if (entity == null) {
      throw new RuntimeException("Request entity cannot be null.");
    }

    this.requestId = entity.getRequestId();
    this.clusterId = entity.getClusterId();

    if (-1L != this.clusterId) {
      try {
        this.clusterName = clusters.getClusterById(this.clusterId).getClusterName();
      } catch (AmbariException e) {
        LOG.debug("Could not load cluster with id {}, the cluster may have been removed for request {}",
            Long.valueOf(clusterId), Long.valueOf(requestId));
      }
    }

    this.createTime = entity.getCreateTime();
    this.startTime = entity.getStartTime();
    this.endTime = entity.getEndTime();
    this.exclusive = entity.isExclusive();
    this.requestContext = entity.getRequestContext();
    this.inputs = entity.getInputs();

    this.requestType = entity.getRequestType();
    this.commandName = entity.getCommandName();
    this.status = entity.getStatus();
    if (entity.getRequestScheduleEntity() != null) {
      this.requestScheduleId = entity.getRequestScheduleEntity().getScheduleId();
    }

    for (StageEntity stageEntity : entity.getStages()) {
      Stage stage = stageFactory.createExisting(stageEntity);
      stages.add(stage);
    }

    resourceFilters = filtersFromEntity(entity);

    operationLevel = operationLevelFromEntity(entity);
  }

  private static List<String> getHostsList(String hosts) {
    List<String> hostList = new ArrayList<String>();
    if (hosts != null && !hosts.isEmpty()) {
      for (String host : hosts.split(",")) {
        if (!host.trim().isEmpty()) {
          hostList.add(host.trim());
        }
      }
    }
    return hostList;
  }

  public Collection<Stage> getStages() {
    return stages;
  }

  public void setStages(Collection<Stage> stages) {
    this.stages = stages;
  }

  public long getRequestId() {
    return requestId;
  }

  public synchronized RequestEntity constructNewPersistenceEntity() {
    RequestEntity requestEntity = new RequestEntity();

    requestEntity.setRequestId(requestId);
    requestEntity.setClusterId(clusterId);
    requestEntity.setCreateTime(createTime);
    requestEntity.setStartTime(startTime);
    requestEntity.setEndTime(endTime);
    requestEntity.setExclusive(exclusive);
    requestEntity.setRequestContext(requestContext);
    requestEntity.setInputs(inputs);
    requestEntity.setRequestType(requestType);
    requestEntity.setRequestScheduleId(requestScheduleId);
    //TODO set all fields

    if (resourceFilters != null) {
      List<RequestResourceFilterEntity> filterEntities = new ArrayList<RequestResourceFilterEntity>();
      for (RequestResourceFilter resourceFilter : resourceFilters) {
        RequestResourceFilterEntity filterEntity = new RequestResourceFilterEntity();
        filterEntity.setServiceName(resourceFilter.getServiceName());
        filterEntity.setComponentName(resourceFilter.getComponentName());
        filterEntity.setHosts(resourceFilter.getHostNames() != null ?
          StringUtils.join(resourceFilter.getHostNames(), ",") : null);
        filterEntity.setRequestEntity(requestEntity);
        filterEntity.setRequestId(requestId);
        filterEntities.add(filterEntity);
      }
      requestEntity.setResourceFilterEntities(filterEntities);
    }

    if (operationLevel != null) {
      RequestOperationLevelEntity operationLevelEntity =
              new RequestOperationLevelEntity();
      operationLevelEntity.setLevel(operationLevel.getLevel().toString());
      operationLevelEntity.setClusterName(operationLevel.getClusterName());
      operationLevelEntity.setServiceName(operationLevel.getServiceName());
      operationLevelEntity.setHostComponentName(operationLevel.getHostComponentName());
      operationLevelEntity.setHostName(operationLevel.getHostName());
      operationLevelEntity.setRequestEntity(requestEntity);
      operationLevelEntity.setRequestId(requestId);
      requestEntity.setRequestOperationLevel(operationLevelEntity);
    }

    return requestEntity;
  }


  public Long getClusterId() {
    return Long.valueOf(clusterId);
  }

  public String getClusterName() {
    return clusterName;
  }

  public String getRequestContext() {
    return requestContext;
  }

  public void setRequestContext(String requestContext) {
    this.requestContext = requestContext;
  }

  public long getCreateTime() {
    return createTime;
  }

  public long getStartTime() {
    return startTime;
  }

  public void setStartTime(long startTime) {
    this.startTime = startTime;
  }

  public long getEndTime() {
    return endTime;
  }

  public void setEndTime(long endTime) {
    this.endTime = endTime;
  }

  public String getInputs() {
    return inputs;
  }

  public void setInputs(String inputs) {
    this.inputs = inputs;
  }

  public List<RequestResourceFilter> getResourceFilters() {
    return resourceFilters;
  }

  public void setResourceFilters(List<RequestResourceFilter> resourceFilters) {
    this.resourceFilters = resourceFilters;
  }

  public RequestOperationLevel getOperationLevel() {
    return operationLevel;
  }

  public void setOperationLevel(RequestOperationLevel operationLevel) {
    this.operationLevel = operationLevel;
  }

  public RequestType getRequestType() {
    return requestType;
  }

  public void setRequestType(RequestType requestType) {
    this.requestType = requestType;
  }

  public String getCommandName() {
    return commandName;
  }

  public void setCommandName(String commandName) {
    this.commandName = commandName;
  }

  public Long getRequestScheduleId() {
    return requestScheduleId;
  }

  public void setRequestScheduleId(Long requestScheduleId) {
    this.requestScheduleId = requestScheduleId;
  }

  public List<HostRoleCommand> getCommands() {
    List<HostRoleCommand> commands = new ArrayList<HostRoleCommand>();
    for (Stage stage : stages) {
      commands.addAll(stage.getOrderedHostRoleCommands());
    }
    return commands;
  }

  @Override
  public String toString() {
    return "Request{" +
        "requestId=" + requestId +
        ", clusterId=" + clusterId +
        ", clusterName='" + clusterName + '\'' +
        ", requestContext='" + requestContext + '\'' +
        ", createTime=" + createTime +
        ", startTime=" + startTime +
        ", endTime=" + endTime +
        ", inputs='" + inputs + '\'' +
        ", resourceFilters='" + resourceFilters + '\'' +
        ", operationLevel='" + operationLevel + '\'' +
        ", requestType=" + requestType +
        ", stages=" + stages +
        '}';
  }

  public HostRoleStatus getStatus() {
    return status;
  }

  public void setStatus(HostRoleStatus status) {
    this.status = status;
  }

  public boolean isExclusive() {
    return exclusive;
  }

  public void setExclusive(boolean isExclusive) {
    this.exclusive = isExclusive;
  }

  /**
   * @param entity  the request entity
   * @return a list of {@link RequestResourceFilter} from the entity, or {@code null}
   *        if none are defined
   */
  public static List<RequestResourceFilter> filtersFromEntity (RequestEntity entity) {
    List<RequestResourceFilter> resourceFilters = null;

    Collection<RequestResourceFilterEntity> resourceFilterEntities = entity.getResourceFilterEntities();
    if (resourceFilterEntities != null) {
      resourceFilters = new ArrayList<RequestResourceFilter>();
      for (RequestResourceFilterEntity resourceFilterEntity : resourceFilterEntities) {
        RequestResourceFilter resourceFilter =
          new RequestResourceFilter(
            resourceFilterEntity.getServiceName(),
            resourceFilterEntity.getComponentName(),
            getHostsList(resourceFilterEntity.getHosts()));
        resourceFilters.add(resourceFilter);
      }
    }

    return resourceFilters;
  }

  /**
   * @param entity  the request entity
   * @return the {@link RequestOperationLevel} from the entity, or {@code null}
   *        if none is defined
   */
  public static RequestOperationLevel operationLevelFromEntity(RequestEntity entity) {
    RequestOperationLevel level = null;

    RequestOperationLevelEntity operationLevelEntity = entity.getRequestOperationLevel();
    if (operationLevelEntity != null) {
      level = new RequestOperationLevel(
          Resource.Type.valueOf(operationLevelEntity.getLevel()),
        operationLevelEntity.getClusterName(),
        operationLevelEntity.getServiceName(),
        operationLevelEntity.getHostComponentName(),
        operationLevelEntity.getHostName());
    }

    return level;
  }



}
