package com.uca.flights;

import java.time.Duration;
import java.util.Iterator;

/**
 * Represent an jump : two step for arrival and departure
 * @Author Cesano Ugo, Colmerauer Clément
 * 
 */
public class Jump implements Iterator<Jump>, Cloneable
{
	/**Jump next jump*/
	private Jump next;
	/**Jump preceding jump*/
	private Jump preced;
	/**Jump departure step*/
	private Step departure;
	/**Jump arrival step*/
	private Step arrival;

	/**
     * Constructor
     * @param departure the departure step
     * @param arrival the arrival step
     * @throws IllegalArgumentException on null parameter or departure after arrival
     */
	public Jump(Step departure, Step arrival)
	{
		if(departure == null)
		{
			throw new IllegalArgumentException("Departure can't be null.");
		}
		if(arrival == null)
		{
			throw new IllegalArgumentException("Arrival can't be null.");
		}

		if(departure.getDuration().compareTo(arrival.getDuration()) > 0)
		{
			throw new IllegalArgumentException("The arrival can't be before the departure");
		}

		this.departure = departure;
		this.arrival   = arrival;

		//Maintain jump and step chaining
		if(this.next != null)
		{
			this.arrival.setNext(this.next.getDeparture());
		}
		else
		{
			this.arrival.setNext(null);
		}
		if(this.preced != null)
		{
			this.preced.getArrival().setNext(this.departure);
		}
	}

	/**
     * Constructor
     * @param departure the departure step
     * @param arrival the arrival step
     * @param next the next jump
     * @throws IllegalArgumentException on null parameter, departure after arrival or next jump before this one
     */
	public Jump(Step departure, Step arrival, Jump next)
	{
		if(departure == null)
		{
			throw new IllegalArgumentException("Departure can't be null.");
		}
		if(arrival == null)
		{
			throw new IllegalArgumentException("Arrival can't be null.");
		}

		if(departure.getDuration().compareTo(arrival.getDuration()) > 0)
		{
			throw new IllegalArgumentException("The arrival can't be before the departure");
		}

		this.next      = next;
		this.departure = departure;
		this.arrival   = arrival;
		this.preced    = null;

		//Maintain jump and step chaining
		if(this.next != null)
		{
			this.arrival.setNext(this.next.getDeparture());
		}
		else
		{
			this.next.setPreced(this);
		}
	}

	/**
     * Setter
     * @param next the next jump
     * @throws IllegalArgumentException on null parameter or next before this one
     */
	void setNext(Jump next)
	{
		if(next == null)
		{
			throw new IllegalArgumentException("Jump can't be null.");
		}
		if(next.getArrival().getDuration().compareTo(this.departure.getDuration()) > 0 || next.getDeparture().getDuration().compareTo(this.departure.getDuration()) > 0)
		{
			throw new IllegalArgumentException("The next jump can't be before this one.");
		}

		if(this.next != next)
		{
			this.next = next;
			next.setPreced(this);
			this.arrival.setNext(next.getDeparture());
		}

	}

	/**
     * Setter
     * @param preced the preceding jump
     * @throws IllegalArgumentException on null parameter or preceding after this one
     */
	void setPreced(Jump preced)
	{
		if(preced == null)
		{
			throw new IllegalArgumentException("Jump can't be null.");
		}
		if(preced.getArrival().getDuration().compareTo(this.departure.getDuration()) < 0 || preced.getDeparture().getDuration().compareTo(this.departure.getDuration()) < 0)
		{
			throw new IllegalArgumentException("The preceding jump can't be after this one.");
		}
		if(this.preced != preced)
		{
			this.preced = preced;
			preced.setNext(this);
			preced.getArrival().setNext(this.departure);
		}
	}

	/**
     * Getter, implementation of iterator
     * @return next jump
     */
	public Jump next()
	{
		return this.next();
	}

	/**
     * Getter, implementation of iterator
     * @return if next jump exist
     */
	public boolean hasNext()
	{
		return this.next != null;
	}

	/**
     * Getter
     * @return preceding jump
     */
	public Jump getPreced()
	{
		return this.preced;
	}

	/**
     * Getter
     * @return duration of jump
     */
	public Duration getDuration()
	{
		return this.arrival.getDuration().minus(this.departure.getDuration());
	}

	/**
     * Delay the duration from another duration
     * @param d the duration
     * @throws IllegalArgumentException on null parameter
     */
	void delay(Duration d)
	{
		if(d == null)
		{
			throw new IllegalArgumentException("Duration can't be null.");
		}
		this.departure.delay(d);
		this.arrival.delay(d);
	}

	/**
     * Getter
     * @return departure step
     */
	Step getDeparture()
	{
		return this.departure;
	}

	/**
     * Getter
     * @return arrival step
     */
	Step getArrival()
	{
		return this.arrival;
	}

	/**
     * Redefinition of clone
     * @return new jump with clone of the jump chain
     */
	@Override 
	public Object clone()
	{
		Jump n = null;
		
		if(this.next != null)
		{
			n = (Jump)next.clone();
		}
		return new Jump((Step)this.departure.clone(),(Step)this.arrival.clone(),n);
	}

	/**
     * Getter
     * @return the first chain
     */
	Iterator<Step> getSteps()
	{
		return this.departure;
	}
}