/**
 * This file is part of Eclipse Steady.
 *
 * Licensed 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.
 *
 * SPDX-License-Identifier: Apache-2.0
 * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors
 */
package org.eclipse.steady.backend.model;

import java.io.Serializable;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.NotNull;

import org.eclipse.steady.backend.model.view.Views;
import org.eclipse.steady.shared.enums.GoalType;

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonView;

/**
 * <p>GoalExecution class.</p>
 */
@JsonInclude(JsonInclude.Include.ALWAYS)
@JsonIgnoreProperties(
    ignoreUnknown = true,
    value = {"createdAt"},
    allowGetters = true)
@Entity
@Table(
    name = "AppGoalExe",
    uniqueConstraints = {
      @UniqueConstraint(columnNames = {"app", "goal", "startedAtClient"}),
      @UniqueConstraint(columnNames = {"executionId"})
    })
public class GoalExecution implements Serializable {

  private static final long serialVersionUID = 1L;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

  @ManyToOne(optional = false)
  @JoinColumn(name = "app", referencedColumnName = "id")
  @JsonBackReference // Required in order to omit the app property when de-serializing JSON
  private Application app;

  @Column(nullable = false, length = 9)
  @Enumerated(EnumType.STRING)
  private GoalType goal;

  /**
   * When the entry was created in the backend.
   */
  @Temporal(TemporalType.TIMESTAMP)
  @JsonFormat(
      shape = JsonFormat.Shape.STRING,
      pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
      timezone = "GMT")
  private java.util.Calendar createdAt;

  /**
   * When the goal was started on client side.
   */
  @Temporal(TemporalType.TIMESTAMP)
  @JsonFormat(
      shape = JsonFormat.Shape.STRING,
      pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
      timezone = "GMT")
  private java.util.Calendar startedAtClient;

  @Column private long runtimeNano;

  /**
   * Client-generated ID.
   */
  @Column private String executionId;

  /**
   * Exception that occurred on client side (if any).
   */
  @Column(columnDefinition = "text")
  @JsonProperty("exception")
  private String exception;

  @Column private long memMax;

  @Column private long memUsedMax;

  @Column private long memUsedAvg;

  @Column private String clientVersion;

  @ManyToMany(
      cascade = {},
      fetch = FetchType.LAZY)
  @JsonView(Views.GoalDetails.class)
  private Collection<Property> configuration;

  // Cache
  @Transient private Map<String, String> configurationMap = null;

  @ManyToMany(
      cascade = {},
      fetch = FetchType.LAZY)
  @JsonView(Views.GoalDetails.class)
  private Collection<Property> systemInfo;

  // Cache
  @Transient private Map<String, String> systemInfoMap = null;

  @ElementCollection
  @CollectionTable(name = "AppGoalExeStatistics")
  @JsonView(Views.GoalDetails.class)
  private Map<String, Long> statistics;

  /**
   * <p>Constructor for GoalExecution.</p>
   */
  public GoalExecution() {
    super();
  }

  /**
   * <p>Constructor for GoalExecution.</p>
   *
   * @param app a {@link org.eclipse.steady.backend.model.Application} object.
   * @param goal a {@link org.eclipse.steady.shared.enums.GoalType} object.
   * @param startedAtClient a {@link java.util.Calendar} object.
   */
  public GoalExecution(Application app, GoalType goal, Calendar startedAtClient) {
    super();
    this.app = app;
    this.goal = goal;
    this.startedAtClient = startedAtClient;
  }

  /**
   * <p>Getter for the field <code>id</code>.</p>
   *
   * @return a {@link java.lang.Long} object.
   */
  public Long getId() {
    return id;
  }
  /**
   * <p>Setter for the field <code>id</code>.</p>
   *
   * @param id a {@link java.lang.Long} object.
   */
  public void setId(Long id) {
    this.id = id;
  }

  /**
   * <p>Getter for the field <code>app</code>.</p>
   *
   * @return a {@link org.eclipse.steady.backend.model.Application} object.
   */
  public Application getApp() {
    return app;
  }
  /**
   * <p>Setter for the field <code>app</code>.</p>
   *
   * @param app a {@link org.eclipse.steady.backend.model.Application} object.
   */
  public void setApp(Application app) {
    this.app = app;
  }

  /**
   * <p>Getter for the field <code>goal</code>.</p>
   *
   * @return a {@link org.eclipse.steady.shared.enums.GoalType} object.
   */
  public GoalType getGoal() {
    return goal;
  }
  /**
   * <p>Setter for the field <code>goal</code>.</p>
   *
   * @param goal a {@link org.eclipse.steady.shared.enums.GoalType} object.
   */
  public void setGoal(GoalType goal) {
    this.goal = goal;
  }

  /**
   * <p>Getter for the field <code>createdAt</code>.</p>
   *
   * @return a {@link java.util.Calendar} object.
   */
  public java.util.Calendar getCreatedAt() {
    return createdAt;
  }
  /**
   * <p>Setter for the field <code>createdAt</code>.</p>
   *
   * @param createdAt a {@link java.util.Calendar} object.
   */
  public void setCreatedAt(java.util.Calendar createdAt) {
    this.createdAt = createdAt;
  }

  /**
   * <p>Getter for the field <code>startedAtClient</code>.</p>
   *
   * @return a {@link java.util.Calendar} object.
   */
  public java.util.Calendar getStartedAtClient() {
    return startedAtClient;
  }
  /**
   * <p>Setter for the field <code>startedAtClient</code>.</p>
   *
   * @param startedAtClient a {@link java.util.Calendar} object.
   */
  public void setStartedAtClient(java.util.Calendar startedAtClient) {
    this.startedAtClient = startedAtClient;
  }

  /**
   * <p>Getter for the field <code>runtimeNano</code>.</p>
   *
   * @return a long.
   */
  public long getRuntimeNano() {
    return runtimeNano;
  }
  /**
   * <p>Setter for the field <code>runtimeNano</code>.</p>
   *
   * @param runtimeNano a long.
   */
  public void setRuntimeNano(long runtimeNano) {
    this.runtimeNano = runtimeNano;
  }

  /**
   * <p>Getter for the field <code>executionId</code>.</p>
   *
   * @return a {@link java.lang.String} object.
   */
  public String getExecutionId() {
    return executionId;
  }
  /**
   * <p>Setter for the field <code>executionId</code>.</p>
   *
   * @param _id a {@link java.lang.String} object.
   */
  public void setExecutionId(String _id) {
    this.executionId = _id;
  }

  /**
   * <p>getExecutionException.</p>
   *
   * @return a {@link java.lang.String} object.
   */
  public String getExecutionException() {
    return exception;
  }
  /**
   * <p>setExecutionException.</p>
   *
   * @param exception a {@link java.lang.String} object.
   */
  public void setExecutionException(String exception) {
    this.exception = exception;
  }

  /**
   * <p>Getter for the field <code>memMax</code>.</p>
   *
   * @return a long.
   */
  public long getMemMax() {
    return memMax;
  }
  /**
   * <p>Setter for the field <code>memMax</code>.</p>
   *
   * @param memMax a long.
   */
  public void setMemMax(long memMax) {
    this.memMax = memMax;
  }

  /**
   * <p>Getter for the field <code>memUsedMax</code>.</p>
   *
   * @return a long.
   */
  public long getMemUsedMax() {
    return memUsedMax;
  }
  /**
   * <p>Setter for the field <code>memUsedMax</code>.</p>
   *
   * @param memUsedMax a long.
   */
  public void setMemUsedMax(long memUsedMax) {
    this.memUsedMax = memUsedMax;
  }

  /**
   * <p>Getter for the field <code>memUsedAvg</code>.</p>
   *
   * @return a long.
   */
  public long getMemUsedAvg() {
    return memUsedAvg;
  }
  /**
   * <p>Setter for the field <code>memUsedAvg</code>.</p>
   *
   * @param memUsedAvg a long.
   */
  public void setMemUsedAvg(long memUsedAvg) {
    this.memUsedAvg = memUsedAvg;
  }

  /**
   * <p>Getter for the field <code>clientVersion</code>.</p>
   *
   * @return a {@link java.lang.String} object.
   */
  public String getClientVersion() {
    return clientVersion;
  }
  /**
   * <p>Setter for the field <code>clientVersion</code>.</p>
   *
   * @param clientVersion a {@link java.lang.String} object.
   */
  public void setClientVersion(String clientVersion) {
    this.clientVersion = clientVersion;
  }

  /**
   * <p>Getter for the field <code>configuration</code>.</p>
   *
   * @return a {@link java.util.Collection} object.
   */
  public Collection<Property> getConfiguration() {
    return configuration;
  }

  /**
   * <p>Setter for the field <code>configuration</code>.</p>
   *
   * @param configuration a {@link java.util.Collection} object.
   */
  public void setConfiguration(Collection<Property> configuration) {
    this.configuration = configuration;
  }

  /**
   * Returns the value of the configuration property with the given name (or null, if no such property exists).
   *
   * @param _name a {@link java.lang.String} object.
   * @return a {@link java.lang.String} object.
   */
  @JsonIgnore
  public String getConfiguration(@NotNull String _name) {
    if (this.configurationMap == null) {
      this.configurationMap = new HashMap<String, String>();
      for (Property p : this.getConfiguration())
        this.configurationMap.put(p.getName(), p.getPropertyValue());
    }
    return this.configurationMap.get(_name);
  }

  /**
   * Returns all configuration properties.
   *
   * @return a {@link java.util.HashMap} object.
   */
  @JsonIgnore
  public Map<String, String> getConfigurationMap() {
    if (this.configurationMap == null) {
      this.configurationMap = new HashMap<String, String>();
      for (Property p : this.getConfiguration())
        this.configurationMap.put(p.getName(), p.getPropertyValue());
    }
    return this.configurationMap;
  }

  /**
   * <p>Getter for the field <code>systemInfo</code>.</p>
   *
   * @return a {@link java.util.Collection} object.
   */
  public Collection<Property> getSystemInfo() {
    return systemInfo;
  }

  /**
   * <p>Setter for the field <code>systemInfo</code>.</p>
   *
   * @param systemInfo a {@link java.util.Collection} object.
   */
  public void setSystemInfo(Collection<Property> systemInfo) {
    this.systemInfo = systemInfo;
  }

  /**
   * Returns the value of the system info with the given name (or null, if no such property exists).
   *
   * @param _name a {@link java.lang.String} object.
   * @return a {@link java.lang.String} object.
   */
  @JsonIgnore
  public String getSystemInfo(@NotNull String _name) {
    if (this.systemInfoMap == null) {
      this.systemInfoMap = new HashMap<String, String>();
      for (Property p : this.getSystemInfo())
        this.systemInfoMap.put(p.getName(), p.getPropertyValue());
    }
    return this.systemInfoMap.get(_name);
  }

  /**
   * <p>Getter for the field <code>statistics</code>.</p>
   *
   * @return a {@link java.util.Map} object.
   */
  public Map<String, Long> getStatistics() {
    return statistics;
  }
  /**
   * <p>Setter for the field <code>statistics</code>.</p>
   *
   * @param statistics a {@link java.util.Map} object.
   */
  public void setStatistics(Map<String, Long> statistics) {
    this.statistics = statistics;
  }

  /**
   * Sets {@link GoalExecution#createdAt}.
   */
  @PrePersist
  public void prePersist() {
    if (this.getCreatedAt() == null) {
      this.setCreatedAt(Calendar.getInstance());
    }
  }

  /** {@inheritDoc} */
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((app == null) ? 0 : app.hashCode());
    result = prime * result + ((executionId == null) ? 0 : executionId.hashCode());
    result = prime * result + ((goal == null) ? 0 : goal.hashCode());
    return result;
  }

  /** {@inheritDoc} */
  @Override
  public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    GoalExecution other = (GoalExecution) obj;
    if (app == null) {
      if (other.app != null) return false;
    } else if (!app.equals(other.app)) return false;
    if (executionId == null) {
      if (other.executionId != null) return false;
    } else if (!executionId.equals(other.executionId)) return false;
    if (goal != other.goal) return false;
    return true;
  }

  /*@Override
  public int hashCode() {
  	final int prime = 31;
  	int result = 1;
  	result = prime * result + ((app == null) ? 0 : app.hashCode());
  	result = prime * result + ((goal == null) ? 0 : goal.hashCode());
  	result = prime * result + ((startedAtClient == null) ? 0 : startedAtClient.hashCode());
  	return result;
  }

  @Override
  public boolean equals(Object obj) {
  	if (this == obj)
  		return true;
  	if (obj == null)
  		return false;
  	if (getClass() != obj.getClass())
  		return false;
  	GoalExecution other = (GoalExecution) obj;
  	if (app == null) {
  		if (other.app != null)
  			return false;
  	} else if (!app.equals(other.app))
  		return false;
  	if (goal != other.goal)
  		return false;
  	if (startedAtClient == null) {
  		if (other.startedAtClient != null)
  			return false;
  	} else if (!startedAtClient.equals(other.startedAtClient))
  		return false;
  	return true;
  }*/

}
