/*
 * This file is part of CraftoPlugin, licensed under the MIT License (MIT).
 *
 * Copyright (c) 2020 CraftolutionDE <https://craftolution.de>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Website: https://craftolution.de/
 * Contact: support@craftolution.de
 */
package de.craftolution.craftoplugin.module.fly;

import de.craftolution.craftoplugin.core.utils.Check;
import de.craftolution.craftoplugin.service.playerstorage.StoredPlayer;

import java.time.Duration;
import java.time.Instant;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;

public class FlyTime {

	public static final Duration INFINITE = Duration.ofSeconds(-1);

	private final int id;
	private final int playerId;
	private final Instant createdAt;
	private Duration duration;
	private Optional<Instant> activatedAt;
	private Optional<Instant> expiredAt;
	private Optional<Instant> revokedAt;

	FlyTime(int id, int playerId, Instant createdAt, Duration duration, Optional<Instant> activatedAt,
	               Optional<Instant> expiredAt, Optional<Instant> revokedAt) {
		Check.nonNulls("createdAt/duration/activatedAt/expiredAt/revokedAt",
			createdAt, duration, activatedAt, expiredAt, revokedAt);
		Check.min(id, 0);
		Check.min(playerId, 0);

		if (duration.isNegative() && !duration.equals(INFINITE)) {
			throw new IllegalArgumentException("negative duration: " + duration);
		}

		this.id = id;
		this.playerId = playerId;
		this.createdAt = createdAt;
		this.duration = duration;
		this.activatedAt = activatedAt;
		this.expiredAt = expiredAt;
		this.revokedAt = revokedAt;
	}

	FlyTime(int id, int playerId, Duration duration) {
		this(id, playerId, Instant.now(), duration, Optional.empty(), Optional.empty(), Optional.empty());
	}

	void updateDuration(Duration newDuration) {
		this.duration = newDuration;
	}

	void activate() {
		this.activatedAt = Optional.of(Instant.now());
	}

	void expire() {
		this.expiredAt = Optional.of(Instant.now());
	}

	void revoke() {
		this.revokedAt = Optional.of(Instant.now());
	}

	public int getId() {
		return id;
	}

	public StoredPlayer getPlayer() throws NoSuchElementException {
		return StoredPlayer.getOrThrow(this.playerId);
	}

	public Instant getCreatedAt() {
		return createdAt;
	}

	public Duration getDuration() {
		return duration;
	}

	public Optional<Instant> getActivatedAt() {
		return activatedAt;
	}

	public Optional<Instant> getExpiredAt() {
		return expiredAt;
	}

	public Optional<Instant> getRevokedAt() {
		return revokedAt;
	}

	public Optional<Instant> getPlannedExpiration() {
		if (this.duration.equals(INFINITE)) { return Optional.empty(); }
		return getActivatedAt().map(instant -> instant.plus(this.duration));
	}

	public boolean hasBeenActivated() {
		return this.getActivatedAt().isPresent();
	}

	public boolean hasExpired() {
		return this.getExpiredAt().isPresent();
	}

	public boolean hasBeenRevoked() {
		return this.getRevokedAt().isPresent();
	}

	public boolean isInfinite() {
		return this.getDuration().equals(INFINITE);
	}

	public Optional<Duration> getTimeLeft() {
		return getPlannedExpiration()
			.map(planned -> Duration.between(Instant.now(), planned))
			.map(dur -> dur.isNegative() ? Duration.ZERO : dur);
	}

	@Override
	public String toString() {
		return "FlyTime{" +
			"id=" + id +
			", playerId=" + playerId +
			", createdAt=" + createdAt +
			", duration=" + duration +
			", activatedAt=" + activatedAt +
			", expiredAt=" + expiredAt +
			", revokedAt=" + revokedAt +
			'}';
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		FlyTime flyTime = (FlyTime) o;
		return id == flyTime.id;
	}

	@Override
	public int hashCode() {
		return Objects.hash(id);
	}

}
