/***
 * 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.service;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.nakedobjects.applib.AbstractDomainObject;
import org.nakedobjects.applib.ApplicationException;
import org.nakedobjects.applib.annotation.Disabled;
import org.nakedobjects.applib.annotation.Hidden;
import org.nakedobjects.applib.annotation.Ignore;
import org.nakedobjects.applib.annotation.MemberOrder;
import org.nakedobjects.applib.snapshot.SnapshottableWithInclusions;
import org.nakedobjects.applib.util.ReasonBuffer;
import org.nakedobjects.applib.util.TitleBuffer;

import com.domainlanguage.time.CalendarInterval;
import com.pragprog.dhnako.carserv.dom.domainservices.MessagePublisher;
import com.pragprog.dhnako.carserv.dom.payment.Payable;
import com.pragprog.dhnako.carserv.dom.payment.PaymentMethod;
import com.pragprog.dhnako.carserv.dom.vehicle.Vehicle;

public abstract class Service extends AbstractDomainObject implements Payable, SnapshottableWithInclusions {


	// {{ Identification
	public String title() {
        TitleBuffer buf = new TitleBuffer();
        if (getVehicle() != null) {
	        buf.append(getVehicle().getRegistrationNumber());
        }
        if (getBookedInAndReady() != null) {
	        buf.append(":", 
	    		getBookedInAndReady().start().toString("MMM dd, yyyy"));
        }
        return buf.toString();
	}
	// }}

	
	// {{ BookedInAndReady
	private CalendarInterval bookedInAndReady;

	@MemberOrder(sequence = "1.1")
	public CalendarInterval getBookedInAndReady() {
		return bookedInAndReady;
	}

	public void setBookedInAndReady(final CalendarInterval bookedInAndReady) {
		this.bookedInAndReady = bookedInAndReady;
	}
	// }}



	// {{ Vehicle
	private ServiceableVehicle vehicle;

	@MemberOrder(sequence = "1.3")
	@Disabled
	public ServiceableVehicle getVehicle() {
		return vehicle;
	}

	public void setVehicle(final ServiceableVehicle vehicle) {
		this.vehicle = vehicle;
	}

	public void modifyVehicle(final ServiceableVehicle vehicle) {
		ServiceableVehicle currentVehicle = getVehicle();
		// check for no-op
		if (vehicle == null || vehicle.equals(currentVehicle)) {
			return;
		}
		// delegate to parent to associate
		vehicle.addToServices(this);
		// additional business logic
		onModifyVehicle(currentVehicle, vehicle);
	}

	public void clearVehicle() {
		ServiceableVehicle currentVehicle = getVehicle();
		// check for no-op
		if (currentVehicle == null) {
			return;
		}
		// delegate to parent to dissociate
		currentVehicle.removeFromServices(this);
		// additional business logic
		onClearVehicle(currentVehicle);
	}

	protected void onModifyVehicle(final ServiceableVehicle oldVehicle, final ServiceableVehicle newVehicle) {
	}

	protected void onClearVehicle(final ServiceableVehicle oldVehicle) {
	}
	// }}


	
	// {{ Amount
	private Integer amount;

	@MemberOrder(sequence = "1.4")
	public Integer getAmount() {
		return amount;
	}

	public void setAmount(final Integer amount) {
		this.amount = amount;
	}

	public String disableAmount() {
		return getPaid()?"Paid":null;
	}
	// }}


	// {{ PaymentMethod
	private PaymentMethod paymentMethod;

	@Disabled
	@MemberOrder(sequence = "1.5")
	public PaymentMethod getPaymentMethod() {
		return paymentMethod;
	}

	public void setPaymentMethod(final PaymentMethod paymentMethod) {
		this.paymentMethod = paymentMethod;
	}

	public void modifyPaymentMethod(final PaymentMethod paymentMethod) {
		PaymentMethod currentPaymentMethod = getPaymentMethod();
		// check for no-op
		if (paymentMethod == null || paymentMethod.equals(getPaymentMethod())) {
			return;
		}
		// associate new
		setPaymentMethod(paymentMethod);
		// additional business logic
		onModifyPaymentMethod(currentPaymentMethod, paymentMethod);
	}

	public void clearPaymentMethod() {
		PaymentMethod currentPaymentMethod = getPaymentMethod();
		// check for no-op
		if (getPaymentMethod() == null) {
			return;
		}
		// dissociate existing
		setPaymentMethod(null);
		// additional business logic
		onClearPaymentMethod(currentPaymentMethod);
	}

	protected void onModifyPaymentMethod(final PaymentMethod oldPaymentMethod, final PaymentMethod newPaymentMethod) {
		try {
			messagePublisher.publish(this);
		} catch (Exception ex) {
			throw new ApplicationException("Failed to publish Service object onto ESB", ex);
		}
	}

	protected void onClearPaymentMethod(final PaymentMethod oldPaymentMethod) {
	}
	// }}


	
	// {{ callback from PaymentMethod once paid
	@Hidden
	public void markAsPaidUsing(PaymentMethod paymentMethod) {
		modifyPaymentMethod(paymentMethod);
	}
	// }}
	

	
	// {{ Paid
	@Disabled
	@MemberOrder(sequence = "1.5.5")
	public Boolean getPaid() {
		return getPaymentMethod() != null;
	}
	// }}


	
	// {{ payUsing
	public void payUsing(final PaymentMethod paymentMethod) {
	    paymentMethod.pay(this);
	}
	public String validatePayUsing(final PaymentMethod paymentMethod) {
	    final ReasonBuffer buf = new ReasonBuffer();

	    buf.appendOnCondition(
	        paymentMethod.getOwner() != getVehicle().getPaymentMethodOwner(), 
	        "Payment method must belong to this customer");
	    
		buf.appendOnCondition(
		        !paymentMethod.getCurrent(), "Payment method must be current");

	    buf.append(paymentMethod.validatePay(this));
	    return buf.getReason();
	}
	public List<PaymentMethod> choices0PayUsing() {
	    return getVehicle().getPaymentMethodOwner().getPaymentMethods();
	}
	public String disablePayUsing() {
	    final ReasonBuffer buf = new ReasonBuffer();
	    buf.appendOnCondition(getAmount() == 0, "Nothing to pay");
	    buf.appendOnCondition(getPaid(), "Already paid");
		return buf.getReason();
	}
	// }}
	
	
	// {{ Lifecycle methods
	public void persisting() {
		getVehicle().addToServices(this);
	}
	// }}


	// {{ snapshotInclusions
	@Ignore
	public List<String> snapshotInclusions() {
		return Arrays.asList("paymentMethod", "vehicle");
	}
	// }}

	
	// {{ dependencies
	private MessagePublisher messagePublisher;
	public void setMessagePublisher(MessagePublisher messagePublisher) {
		this.messagePublisher = messagePublisher;
	}
	// }}
}
