/*
 * Copyright (c) 2011, Patrik Dufresne. All rights reserved.
 * Patrik Dufresne PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.planimod.ui.views.production;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.databinding.observable.map.WritableMap;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.observable.set.ISetChangeListener;
import org.eclipse.core.databinding.observable.set.SetChangeEvent;

import com.patrikdufresne.managers.IManagerObserver;
import com.patrikdufresne.managers.ManagerEvent;
import com.patrikdufresne.managers.ManagerException;
import com.planimod.core.PlanimodManagers;
import com.planimod.core.Product;
import com.planimod.core.ProductionEvent;
import com.planimod.core.Shift;
import com.planimod.core.comparators.ProductionEventComparators;

/**
 * This class provide an observable map between a shift object and a list of
 * production events represented by a string. i.e. This class is wrapping a
 * Map<Shift, String>
 * 
 * @author Patrik Dufresne
 * 
 */
public class ShiftProductionEventObservableMap extends WritableMap {

	/**
	 * Private interface to avoid exposing observers implementation.
	 * 
	 * @author Patrik Dufresne
	 * 
	 */
	private class PrivateInterface implements IManagerObserver,
			ISetChangeListener {

		/**
		 * Public constructor
		 */
		public PrivateInterface() {
			// Nothing to do
		}

		@Override
		public void handleManagerEvent(ManagerEvent event) {
			getRealm().exec(new Runnable() {
				@Override
				public void run() {
					doCompute();
				}
			});
		}

		@Override
		public void handleSetChange(SetChangeEvent event) {
			getRealm().exec(new Runnable() {
				@Override
				public void run() {
					doCompute();
				}
			});
		}
	}

	private PlanimodManagers managers;

	private PrivateInterface privateInterface = new PrivateInterface();

	private IObservableSet shifts;

	/**
	 * Create a new observable map to provide labels
	 * 
	 * @param shifts
	 *            the list of shifts
	 * @param productionEventsMap
	 *            an observable map between the production events and number of
	 *            required position.
	 */
	public ShiftProductionEventObservableMap(PlanimodManagers managers,
			IObservableSet shifts) {
		super(Shift.class, String.class);
		this.managers = managers;
		this.shifts = shifts;
		doCompute();
	}

	@Override
	public void clear() {
		throw new UnsupportedOperationException();
	}

	/**
	 * Make this observable map dirty.
	 */
	protected void doCompute() {

		Map<ProductionEvent, Long> table;
		if (this.shifts.size() <= 0) {
			// There is not shift, so clear the map.
			super.clear();
			return;
		}

		try {
			table = this.managers.getProductionEventManager()
					.listProductionEventTableByShifts(this.shifts);
		} catch (ManagerException e) {
			e.printStackTrace();
			return;
		}

		Map<Shift, String> map = new HashMap<Shift, String>();

		for (Object obj : this.shifts) {
			Shift s = (Shift) obj;

			StringBuffer buf = new StringBuffer();
			List<ProductionEvent> relatedEvents = new ArrayList<ProductionEvent>();
			for (Entry<ProductionEvent, Long> entry : table.entrySet()) {
				if (entry.getKey().getShift().equals(s)) {
					relatedEvents.add(entry.getKey());
				}
			}

			// Build the labels for the shift.
			Collections.sort(relatedEvents,
					ProductionEventComparators.byRefIdFamilyName());
			for (ProductionEvent prodEvent : relatedEvents) {
				Product product = prodEvent.getProduct();
				String family = product.getFamily() == null
						|| product.getFamily().isEmpty() ? null : product
						.getFamily();
				String name = product.getName() == null
						|| product.getName().isEmpty() ? null : product
						.getName();
				String refId = product.getRefId() == null
						|| product.getRefId().isEmpty() ? null : product
						.getRefId();
				if (family != null) {
					buf.append(family);
					if (refId != null) {
						buf.append(" - "); //$NON-NLS-1$
						buf.append(refId);
					}
				} else if (name != null) {
					buf.append(name);
					if (refId != null) {
						buf.append(" - "); //$NON-NLS-1$
						buf.append(refId);
					}
				} else if (refId != null) {
					buf.append(refId);
				} else {
					// TODO Localize this string.
					buf.append("unknown"); //$NON-NLS-1$
				}
				buf.append(" ("); //$NON-NLS-1$
				buf.append(table.get(prodEvent));
				buf.append(")\r\n"); //$NON-NLS-1$
			}

			map.put(s, buf.toString());

		}
		// Update the map with new labels.
		super.putAll(map);

	}

	@Override
	protected void firstListenerAdded() {
		super.firstListenerAdded();
		this.managers.addObserver(ManagerEvent.ALL, ProductionEvent.class,
				this.privateInterface);
		this.shifts.addSetChangeListener(this.privateInterface);
	}

	@Override
	protected void lastListenerRemoved() {
		super.lastListenerRemoved();
		this.managers.removeObserver(ManagerEvent.ALL, ProductionEvent.class,
				this.privateInterface);
		this.shifts.removeSetChangeListener(this.privateInterface);
	}

	@Override
	public Object put(Object key, Object value) {
		throw new UnsupportedOperationException();
	}

	@Override
	public void putAll(Map arg0) {
		throw new UnsupportedOperationException();
	}

	@Override
	public Object remove(Object key) {
		throw new UnsupportedOperationException();
	}

}
