package com.uca.flights;

import java.util.LinkedList;
import java.time.Duration;

/**
 * Tool to build a trip
 * @Author Cesano Ugo, Colmerauer Clément
 * 
 */
public class TripBuilder implements Cloneable
{
	/** List of step used to build*/
	private LinkedList<Step> steps;
	/** If false, each duration are considered relative from first take off and duration coherency it up to dev. If true all duration are considered as absoulte*/
	private boolean          absoluteDuration = false; 

	/**
     * Constructor
     */
	public TripBuilder()
	{
		this.steps = new LinkedList<Step>();
	}

	/**
     * Constructor
     * @param tb a tribbuilder
     * @throws IllegalArgumentException on null parameter
     */
	public TripBuilder(TripBuilder tb)
	{
		if(tb == null)
		{
			throw new IllegalArgumentException("Tripbuilder can't be null.");
		}

		this.steps = new LinkedList<Step>();
		this.append(tb);
	}

	/**
     * Constructor
     * @param trip a trip
     * @throws IllegalArgumentException on null parameter
     */
	public TripBuilder(Trip trip)
	{
		if(trip == null)
		{
			throw new IllegalArgumentException("Trip can't be null.");
		}
		this.steps = new LinkedList<Step>();

		append(trip);

	}

	/**
     * Constructor
     * @param j a jump
     * @throws IllegalArgumentException on null parameter
     */
	public TripBuilder(Jump j)
	{
		if(j == null)
		{
			throw new IllegalArgumentException("Jump can't be null.");
		}
		
		this.steps = new LinkedList<Step>();
		append(j);
	}

	/**
     * Constructor
     * @param s a step
     * @throws IllegalArgumentException on null parameter
     */
	public TripBuilder(Step s)
	{
		if(s == null)
		{
			throw new IllegalArgumentException("Step can't be null.");
		}

		this.steps = new LinkedList<Step>();
		append(s);
	}

	/**
     * Constructor
     * @param a an airport
     * @param d a duration
     * @throws IllegalArgumentException on null parameter
     */
	public TripBuilder(Airport a, Duration d)
	{
		if(a == null)
		{
			throw new IllegalArgumentException("Airport can't be null");
		}
		if(d == null)
		{
			throw new IllegalArgumentException("Duration can't be null");
		}

		this.steps = new LinkedList<Step>();
		append(a,d);
	}

	/**
     * Concatenate the two tripbilder
     * @param tb a tribbuilder
     * @throws IllegalArgumentException on null or empty parameter
     */
	public void append(TripBuilder tb)
	{
		if(tb == null)
		{
			throw new IllegalArgumentException("Tripbuilder can't be null.");
		}
		if(tb.getSteps().isEmpty())
		{
			throw new IllegalArgumentException("Tripbuilder is empty.");
		}

		LinkedList<Step> ls = tb.getSteps();
		for(int i = 0 ; i < ls.size() ; i++)
		{
			this.steps.add((Step)ls.get(i).clone());
		}
	}

	/**
     * Concatenate the two tripbilder
     * @param tb a tribbuilder
     * @throws IllegalArgumentException on null or empty parameter
     */
	public void prepend(TripBuilder tb)
	{
		if(tb == null)
		{
			throw new IllegalArgumentException("Tripbuilder can't be null.");
		}
		if(tb.getSteps().isEmpty())
		{
			throw new IllegalArgumentException("Tripbuilder is empty.");
		}

		LinkedList<Step> ls = tb.getSteps();
		for(int i = ls.size()-1 ; i >= 0 ; i--)
		{
			this.steps.add((Step)ls.get(i).clone());
		}
	}

	/**
     * Add a trip to the trip builder
     * @param t a trip
     * @throws IllegalArgumentException on null parameter
     */
	public void append(Trip t)
	{
		if(t == null)
		{
			throw new IllegalArgumentException("Trip can't be null.");
		}

		Step s = t.getStart().getDeparture();
		this.steps.add(s);
		while(s.hasNext())
		{
			s = s.next();
			this.steps.add(s);
		}
	}

	/**
     * Add a trip to the trip builder
     * @param t a trip
     * @throws IllegalArgumentException on null parameter
     */
	public void prepend(Trip t)
	{
		if(t == null)
		{
			throw new IllegalArgumentException("Trip can't be null.");
		}

		LinkedList<Step> ls = new LinkedList<Step>();

		Step s = t.getStart().getDeparture();
		this.steps.add(s);
		while(s.hasNext())
		{
			s = s.next();
			ls.add(s);
		}

		while(!ls.isEmpty())
		{
			this.steps.add(0,ls.getLast());
			ls.remove(ls.size()-1);
		}
	}

	/**
     * Add a jump to the trip builder
     * @param j a jump
     * @throws IllegalArgumentException on null parameter
     */
	public void append(Jump j)
	{
		if(j == null)
		{
			throw new IllegalArgumentException("Jump can't be null.");
		}

		this.steps.getLast().setNext(j.getDeparture());
		this.steps.add(j.getDeparture());
		this.steps.add(j.getArrival());
	}

	/**
     * Add a jump to the trip builder
     * @param j a jump
     * @throws IllegalArgumentException on null parameter
     */
	public void prepend(Jump j)
	{
		if(j == null)
		{
			throw new IllegalArgumentException("Jump can't be null.");
		}

		j.getArrival().setNext(this.steps.getFirst());
		this.steps.add(0,j.getArrival());
		this.steps.add(0,j.getDeparture());
	}

	/**
     * Add a step to the trip builder
     * @param s a step
     * @throws IllegalArgumentException on null parameter
     */
	public void append(Step s)
	{
		if(s == null)
		{
			throw new IllegalArgumentException("Step can't be null.");
		}

		this.steps.getLast().setNext(s);
		this.steps.add(s);
	}

	/**
     * Add a step to the trip builder
     * @param s a step
     * @throws IllegalArgumentException on null parameter
     */
	public void prepend(Step s)
	{
		if(s == null)
		{
			throw new IllegalArgumentException("Step can't be null.");
		}

		s.setNext(this.steps.getFirst());
		this.steps.add(0,s);
	}

	/**
     * Add a step to the trip builder
     * @param a an airport
     * @param d a duration
     * @throws IllegalArgumentException on null parameter
     */
	public void append(Airport a, Duration d)
	{
		if(a == null)
		{
			throw new IllegalArgumentException("Airport can't be null");
		}
		if(d == null)
		{
			throw new IllegalArgumentException("Duration can't be null");
		}

		this.append(new Step(a,d));
	}

	/**
     * Add a step to the trip builder
     * @param a an airport
     * @param d a duration
     * @throws IllegalArgumentException on null parameter
     */
	public void prepend(Airport a, Duration d)
	{
		if(a == null)
		{
			throw new IllegalArgumentException("Airport can't be null");
		}
		if(d == null)
		{
			throw new IllegalArgumentException("Duration can't be null");
		}
		this.prepend(new Step(a,d));
	}

	/**
     * Remove the last step of the trip
     * @throws IllegalArgumentException if empty
     */
	public void removeLast()
	{
		if(this.steps.isEmpty())
		{
			throw new IllegalStateException("Doesn't have element to remove.");
		}
		this.steps.remove(this.steps.size()-1);
	}

	/**
     * Remove the first step of the trip
     * @throws IllegalArgumentException if empty
     */
	public void removeFirst()
	{
		if(this.steps.isEmpty())
		{
			throw new IllegalStateException("Doesn't have element to remove.");
		}
		this.steps.remove(0);
	}

	/**
     * Clear the trip
     */
	public void clear()
	{
		this.steps = new LinkedList<Step>();
	}

	/**
     * Pass all duration from absolute to relative
     * @throws IllegalArgumentException if empty
     */
	public void updateDuration()
	{
		if(!this.steps.isEmpty() && this.steps.size() <= 1)
		{
			LinkedList<Step> s = new LinkedList<Step>();
			s.add(new Step(this.steps.getFirst().getAirport(),Duration.ZERO));
			this.steps.get(1).delay(this.steps.getFirst().getDuration());
			for(int i = 1 ; i < this.steps.size() ; i++)
			{
				s.add(new Step(this.steps.get(i).getAirport(),s.getLast().getDuration().plus(this.steps.get(i).getDuration())));
			}
		}
		else
		{
			throw new IllegalStateException("Not enough element to update trip."); 
		}
	}

	/**
     * Create a trip
     * @return a new trip
     * @throws IllegalArgumentException if empty
     */
	public Trip toTrip()
	{
		if(this.steps.size() <= 1)
		{
			throw new IllegalStateException("Not enough element to build trip.");
		}
		try
		{
			if(absoluteDuration)
			{
				updateDuration();
			}
			LinkedList<Jump> jumps  = new LinkedList<Jump>();
			Step             preced = this.steps.get(0);
			Step             act    = this.steps.get(1);
			Step             next;
			Jump             j      = new Jump(preced,act);
			jumps.add(j);
			while(act.hasNext())
			{
				act    = act.next();
				preced = preced.next();
				j      = new Jump(preced,act);
			}
			return new Trip(jumps.getFirst(),true);
		}
		catch(IllegalArgumentException e)
		{
			throw new IllegalStateException("Incoherent duration.");
		}
	}

	/**
     * Create a regular trip
     * @return a new regular trip
     * @throws IllegalArgumentException if empty
     */
	public Trip toRegularTrip()
	{
		if(this.steps.size() <= 1)
		{
			throw new IllegalStateException("Not enough element to build trip.");
		}
		try
		{
			if(absoluteDuration)
			{
				updateDuration();
			}
			LinkedList<Jump> jumps  = new LinkedList<Jump>();
			Step             preced = this.steps.get(0);
			Step             act    = this.steps.get(1);
			Step             next;
			Jump             j      = new Jump(preced,act);
			jumps.add(j);
			while(act.hasNext())
			{
				act    = act.next();
				preced = preced.next();
				j      = new Jump(preced,act);
			}
			return new Trip(jumps.getFirst(),false);
		}
		catch(IllegalArgumentException e)
		{
			throw new IllegalStateException("Incoherent duration.");
		}
	}

	/**
     * Redefinition of clone
     * @return a deep copy of this
     */
	@Override
	public Object clone()
	{
		return new TripBuilder(this);
	}

	/**
     * Getter
     * @return a shallow copy the list of steps
     */
	private LinkedList<Step> getSteps()
	{
		return new LinkedList<Step>(this.steps);
	}

	/**
     * Getter
     * @return if absolute duration are used
     */
	public boolean useAbsoluteDuration()
	{
		return this.absoluteDuration;
	}

	/**
     * Setter
     * @param b a boolean
     */
	public void setAbsoluteDuration(boolean b)
	{
		this.absoluteDuration = b;
	}
}