/***
 * Excerpted from "Domain-Driven Design Using Naked Objects",
 * published by The Pragmatic Bookshelf.
 * Copyrights apply to this code. It may not be used to create training material, 
 * courses, books, articles, and the like. Contact us if you are in doubt.
 * We make no guarantees that this code is fit for any purpose. 
 * Visit http://www.pragmaticprogrammer.com/titles/dhnako for more book information.
***/
package com.pragprog.dhnako.carserv.dom.vehicle;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.TreeSet;

import org.nakedobjects.applib.AbstractDomainObject;
import org.nakedobjects.applib.annotation.Disabled;
import org.nakedobjects.applib.annotation.Hidden;
import org.nakedobjects.applib.annotation.MemberOrder;
import org.nakedobjects.applib.annotation.When;
import org.nakedobjects.applib.util.TitleBuffer;

import com.domainlanguage.time.CalendarDate;
import com.pragprog.dhnako.carserv.dom.domainservices.CalendarService;
import com.pragprog.dhnako.carserv.dom.payment.PaymentMethodOwner;
import com.pragprog.dhnako.carserv.dom.service.InitialService;
import com.pragprog.dhnako.carserv.dom.service.RegularService;
import com.pragprog.dhnako.carserv.dom.service.Service;

public abstract class Vehicle extends AbstractDomainObject {

	// {{ Identification
	public String title() {
		final TitleBuffer buf = new TitleBuffer();
		buf.append(getRegistrationNumber());
		return buf.toString();
	}
	// }}

	// {{ RegistrationNumber
	private RegistrationNumber registrationNumber;

	@MemberOrder(sequence = "1.1")
	@Disabled(When.ONCE_PERSISTED)
	public RegistrationNumber getRegistrationNumber() {
		return registrationNumber;
	}

	public void setRegistrationNumber(final RegistrationNumber registrationNumber) {
		this.registrationNumber = registrationNumber;
	}
	// }}
	
	
	// {{ Owner
	private VehicleOwner owner;

	@MemberOrder(sequence = "1.2")
	@Disabled(When.ONCE_PERSISTED)
	public VehicleOwner getOwner() {
		return owner;
	}

	public void setOwner(final VehicleOwner owner) {
		this.owner = owner;
	}

	public void modifyOwner(final VehicleOwner owner) {
		VehicleOwner currentOwner = getOwner();
		// check for no-op
		if (owner == null || owner.equals(currentOwner) ) {
			return;
		}
		// delegate to parent to associate
		owner.addToVehicles(this);
		// additional business logic
		onModifyOwner(currentOwner, owner);
	}

	public void clearOwner() {
		VehicleOwner currentOwner = getOwner();
		// check for no-op
		if (currentOwner == null) {
			return;
		}
		// delegate to parent to dissociate
		currentOwner.removeFromVehicles(this);
		// additional business logic
		onClearOwner(currentOwner);
	}

	protected void onModifyOwner(final VehicleOwner oldOwner, final VehicleOwner newOwner) {
	}

	protected void onClearOwner(final VehicleOwner oldOwner) {
	}

	public String validateOwner(final VehicleOwner owner) {
		if (owner != null) {
			return owner.validateAddToVehicles(this);
		} else {
			// clearing association
			if (getOwner() == null) {
				return null;
			}
			return getOwner().validateRemoveFromVehicles(this);
		}
	}
	// }}
	
	
	// {{ PaymentMethodOwner (derived)
	@Hidden
	public PaymentMethodOwner getPaymentMethodOwner() {
		return getOwner();
	}
	// }}



	// {{ Services
	private List<Service> services = new ArrayList<Service>();

	@MemberOrder(sequence = "1.4")
	@Disabled
	public List<Service> getServices() {
		return services;
	}

	public void setServices(final List<Service> services) {
		this.services = services;
	}

	public void addToServices(final Service service) {
		// check for no-op
		if (service == null
				|| getServices().contains(service)) {
			return;
		}
		// dissociate arg from its current parent (if any).
		service.clearVehicle();
		// associate arg
		service.setVehicle(this);
		getServices().add(service);
		// additional business logic
		onAddToServices(service);
	}

	public void removeFromServices(
			final Service service) {
		// check for no-op
		if (service == null
				|| !getServices().contains(service)) {
			return;
		}
		// dissociate arg
		service.setVehicle(null);
		getServices().remove(service);
		// additional business logic
		onRemoveFromServices(service);
	}

	protected void onAddToServices(final Service service) {
	}

	protected void onRemoveFromServices(final Service service) {
	}
	// }}


	// {{ Model
	private Model model;

	@MemberOrder(sequence = "1.3")
	@Disabled(When.ONCE_PERSISTED)
	public Model getModel() {
		return model;
	}

	public void setModel(final Model model) {
		this.model = model;
	}
	// }}

	

	// {{ bookService
	@MemberOrder(sequence="1.1")
	@Disabled(When.UNTIL_PERSISTED)
	public RegularService bookService( ) {
	    RegularService service = newTransientInstance(RegularService.class);
	    service.setVehicle(this);
	    return service;
	}
	// }}
	
	
	// {{ delete
	@MemberOrder(sequence = "1.2")
	@Disabled(When.UNTIL_PERSISTED)
	public void delete() {
		clearOwner();
		getContainer().remove(this);
	}
	// }}

	
	// {{ mostRecentService
	@MemberOrder(sequence = "1.3")
	@Disabled(When.UNTIL_PERSISTED)
	public Service mostRecentService() {
	    TreeSet<Service> sortedServices = 
	        new TreeSet<Service>(new Comparator<Service>() {
	            public int compare(Service s, Service t) {
	                return s.getBookedInAndReady().compareTo(
	                       t.getBookedInAndReady());
	            }
	    });
	    sortedServices.addAll(getServices());
	    return sortedServices.size() > 0? sortedServices.last(): null;
	}
	// }}

	
	// {{ isServiceDue
	@Hidden
	public boolean isServiceOverdue() {
		// no need to check for mostRecentService() is null;
		// null object pattern means there will always be a service 
		// of some sort.
	    final CalendarDate lastService = 
	        mostRecentService().getBookedInAndReady().start();
	    final CalendarDate serviceDue = 
	        lastService.plusMonths(model.getServiceInterval());
	    return calendarService.today().isAfter(serviceDue);
	}
	// }}

	
	
	// {{ lifecycle methods
	public void created() {
		addToServices(newTransientInstance(InitialService.class));
	}
	public void persisting() {
		getOwner().addToVehicles(this);
	}
	// }}



	// {{ injected: CalendarService
	private CalendarService calendarService;

	public void setCalendarService(final CalendarService calendarService) {
		this.calendarService = calendarService;
	}
	// }}



}