//------------------------------------------------------------------------------
// Copyright (c) 2005, 2006 IBM Corporation and others.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// Contributors:
// IBM Corporation - initial implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.library.edit.util;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.util.AbstractTreeIterator;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.epf.library.edit.util.model.ModelFactory;
import org.eclipse.epf.library.edit.util.model.OrderInfo;
import org.eclipse.epf.library.edit.util.model.OrderInfoCollection;
import org.eclipse.epf.library.edit.util.model.util.StringResource;
import org.eclipse.epf.uma.ContentElement;
import org.eclipse.epf.uma.Section;
import org.eclipse.epf.uma.VariabilityElement;
import org.eclipse.epf.uma.VariabilityType;
import org.eclipse.epf.uma.util.AssociationHelper;
import org.eclipse.epf.uma.util.ContentDescriptionFactory;
import org.eclipse.epf.uma.util.UmaUtil;


/**
 * 
 * This class encapsulates all the logics used to retrieve the list of all
 * contributed/inherited sections of the given element as well as allows
 * manipulate this list.
 * 
 * @author Phong Nguyen Le
 * @since 1.0
 */
public class SectionList extends BasicEList {

	/**
	 * 
	 */
	private static final long serialVersionUID = 3257572797487069233L;

	private static final String ORDER_INFO_NAME = "sections"; //$NON-NLS-1$

	private static final Map DEFAULT_SAVE_OPTIONS = new HashMap();

	public static final int STEPS_FOR_ELEMENT_ONLY = 1;

	public static final int STEPS_FOR_ELEMENT_AND_PARENTS = 2;

	static {
		DEFAULT_SAVE_OPTIONS.put(XMLResource.OPTION_ENCODING, "ASCII"); //$NON-NLS-1$
	}

	private ContentElement editElement;

	private boolean mixed = true;

	private boolean changed = false;

	private static boolean isContributor(VariabilityElement e) {
		return TngUtil.isContributor(e);
	}

	public SectionList(ContentElement e, int scope) {
		editElement = e;
		if (scope == STEPS_FOR_ELEMENT_ONLY) {
			mixed = false;
		} else if (scope == STEPS_FOR_ELEMENT_AND_PARENTS) {
			calculateParentsOnly(e);
		} else {
			mixed = false;
		}
	}

	private void calculateParentsOnly(ContentElement e) {
		Iterator iter = null;
		// if(isContributor(e)) {
		// // System.out.println("$$$ for " + e.getName() + " = contributing is
		// true");
		// List supers = new ArrayList();
		// UmaUtil.getAllSupers(supers, e, VariabilityType.CONTRIBUTES_LITERAL);
		// supers.add(e);
		// iter = supers.iterator();
		// } else if(isExtended(e)) {
		// // System.out.println("$$$ for " + e.getName() + " = extending is
		// true");
		// List supers = new ArrayList();
		// UmaUtil.getAllSupers(supers, e, VariabilityType.EXTENDS_LITERAL);
		// supers.add(e);
		// iter = supers.iterator();
		// }
		if (isContributor(e) || isExtended(e)) {
			// System.out.println("$$$ for " + e.getName() + " = contributing is
			// true");
			List supers = new ArrayList();
			UmaUtil.getAllSupersBoth(supers, e,
					VariabilityType.CONTRIBUTES_LITERAL,
					VariabilityType.EXTENDS_LITERAL);
			supers.add(e);
			iter = supers.iterator();
		} else {
			mixed = false;
		}

		if (mixed) {
			// create a map of GUID / contributor
			//
			OrderInfo latestInfo = null;
			Map guidSectionMap = new HashMap();
			List sects = new LinkedList();
			while (iter.hasNext()) {
				ContentElement element = (ContentElement) iter.next();
				if (ContentDescriptionFactory.hasPresentation(element)) {
					for (Iterator iterator = element.getPresentation()
							.getSections().iterator(); iterator.hasNext();) {
						Section sect = (Section) iterator.next();
						guidSectionMap.put(sect.getGuid(), sect);
						sects.add(sect);
					}
				}
				OrderInfo orderInfo = TngUtil.getOrderInfo(element,
						ORDER_INFO_NAME);
				if (orderInfo != null) {
					if (latestInfo == null
							|| orderInfo.getTimestamp() > latestInfo
									.getTimestamp()) {
						latestInfo = orderInfo;
					}
				}
			}

			if (latestInfo != null) {
				// reorder the sections based on the latest order info
				//
				int size = latestInfo.getGUIDs().size();
				for (int i = 0; i < size; i++) {
					Object guid = latestInfo.getGUIDs().get(i);
					Object sect = guidSectionMap.get(guid);
					if (sect != null) {
						super.add(sect);
						sects.remove(sect);
					}
				}
			}
			super.addAll(sects);
		}
	}

	// deprecate the following constructor
	public SectionList(ContentElement e) {
		editElement = e;
		Iterator iter = null;
		if (isContributor(e) || TngUtil.hasContributor(e)) {
			VariabilityElement base = TngUtil.getBase(e);
			iter = new AbstractTreeIterator(base) {

				protected Iterator getChildren(Object object) {
					List children = new ArrayList();
					for (Iterator iterator = AssociationHelper
							.getImmediateVarieties((VariabilityElement) object)
							.iterator(); iterator.hasNext();) {
						VariabilityElement element = (VariabilityElement) iterator
								.next();
						if (element.getVariabilityType() == VariabilityType.CONTRIBUTES_LITERAL) {
							children.add(element);
						}
					}
					return children.iterator();
				}

			};
		} else if (isExtended(e)) {
			System.out
					.println("$$$ for " + e.getName() + " = extended is true"); //$NON-NLS-1$ //$NON-NLS-2$
			List supers = new ArrayList();
			UmaUtil.getAllSupers(supers, e, VariabilityType.EXTENDS_LITERAL);
			supers.add(e);
			iter = supers.iterator();
		} else {
			mixed = false;
		}

		if (mixed) {
			// create a map of GUID / contributor
			//
			OrderInfo latestInfo = null;
			Map guidSectionMap = new HashMap();
			List sects = new LinkedList();
			while (iter.hasNext()) {
				ContentElement element = (ContentElement) iter.next();
				if (ContentDescriptionFactory.hasPresentation(element)) {
					for (Iterator iterator = element.getPresentation()
							.getSections().iterator(); iterator.hasNext();) {
						Section sect = (Section) iterator.next();
						guidSectionMap.put(sect.getGuid(), sect);
						sects.add(sect);
					}
				}
				OrderInfo orderInfo = TngUtil.getOrderInfo(element,
						ORDER_INFO_NAME);
				if (orderInfo != null) {
					if (latestInfo == null
							|| orderInfo.getTimestamp() > latestInfo
									.getTimestamp()) {
						latestInfo = orderInfo;
					}
				}
			}

			if (latestInfo != null) {
				// reorder the sections based on the latest order info
				//
				int size = latestInfo.getGUIDs().size();
				for (int i = 0; i < size; i++) {
					Object guid = latestInfo.getGUIDs().get(i);
					Object sect = guidSectionMap.get(guid);
					if (sect != null) {
						super.add(sect);
						sects.remove(sect);
					}
				}
			}
			super.addAll(sects);
		}
		// else {
		// // addAll(editElement.getPresentation().getSections());
		// mixed = false;
		// }
	}

	private static boolean isExtended(ContentElement e) {
		return e.getVariabilityBasedOnElement() != null
				&& e.getVariabilityType() == VariabilityType.EXTENDS_LITERAL;
	}

	/**
	 * Applies recent changes in the list
	 * 
	 */
	public void apply() {
		if (!mixed || !changed)
			return;

		// save the order info to the orderingGuide of the editElement
		//
		String str = editElement.getOrderingGuide();
		OrderInfoCollection orderInfos = null;
		StringResource res = null;
		if (str == null || str.length() == 0) {
			orderInfos = ModelFactory.eINSTANCE.createOrderInfoCollection();
		} else {
			res = new StringResource(str);
			try {
				res.load(null);
				if (res.getContents().isEmpty()) {
					orderInfos = ModelFactory.eINSTANCE
							.createOrderInfoCollection();
				} else {
					orderInfos = (OrderInfoCollection) res.getContents().get(0);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		// find the order infor for sections
		//
		OrderInfo sectOrderInfo = null;
		for (Iterator iter = orderInfos.getOrderInfos().iterator(); iter
				.hasNext();) {
			OrderInfo orderInfo = (OrderInfo) iter.next();
			if (ORDER_INFO_NAME.equalsIgnoreCase(orderInfo.getName())) {
				sectOrderInfo = orderInfo;
				break;
			}
		}

		if (sectOrderInfo == null) {
			sectOrderInfo = ModelFactory.eINSTANCE.createOrderInfo();
			sectOrderInfo.setName(ORDER_INFO_NAME);
			orderInfos.getOrderInfos().add(sectOrderInfo);
		} else {
			sectOrderInfo.getGUIDs().clear();
		}

		int size = size();
		for (int i = 0; i < size; i++) {
			Section sect = (Section) get(i);
			sectOrderInfo.getGUIDs().add(sect.getGuid());
		}
		sectOrderInfo.setTimestamp(System.currentTimeMillis());
		if (res == null) {
			res = new StringResource(null);
			res.getContents().add(orderInfos);
		}
		try {
			res.save(DEFAULT_SAVE_OPTIONS);
			str = res.getString();
			// System.out.println("SectionList.apply(): new ordering guide");
			// System.out.println("------ orderingGuide start ------");
			// System.out.println(str);
			// System.out.println("------ orderingGuide end ------");
			editElement.setOrderingGuide(str);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (RuntimeException e) {
			e.printStackTrace();
		}
	}

	public boolean canRemove(Section section) {
		if (ContentDescriptionFactory.hasPresentation(editElement)) {
			return editElement.getPresentation() == section.eContainer();
		}
		return false;
	}

	public Object remove(int index) {
		if (mixed) {
			if (!canRemove((Section) get(index)))
				return null;
			Object removed = super.remove(index);
			editElement.getPresentation().getSections().remove(removed);
			return removed;
		} else {
			return editElement.getPresentation().getSections().remove(index);
		}
	}

	public boolean remove(Object o) {
		if (!canRemove((Section) o))
			return false;
		if (mixed) {
			if (super.remove(o)) {
				editElement.getPresentation().getSections().remove(o);
				return true;
			}
			return false;
		}
		return editElement.getPresentation().getSections().remove(o);
	}

	public boolean removeAll(Collection c) {
		if (mixed) {
			boolean modified = false;
			Iterator e = iterator();
			while (e.hasNext()) {
				Object o = e.next();
				if (c.contains(o) && canRemove((Section) o)) {
					e.remove();
					editElement.getPresentation().getSections().remove(o);
					modified = true;
				}
			}
			return modified;
		} else {
			return editElement.getPresentation().getSections().removeAll(c);
		}
	}

	public void add(int index, Object element) {
		if (mixed) {
			super.add(index, element);
			editElement.getPresentation().getSections().add(element);
			changed = true;
		} else {
			editElement.getPresentation().getSections().add(index, element);
		}
	}

	public boolean add(Object o) {
		boolean b = editElement.getPresentation().getSections().add(o);
		if (mixed) {
			b = super.add(o);
			if (b)
				changed = true;
		}
		return b;
	}

	public boolean addAll(Collection c) {
		boolean b = editElement.getPresentation().getSections().addAll(c);
		if (mixed) {
			b = super.addAll(c);
			if (b)
				changed = true;
		}
		return b;
	}

	public boolean addAll(int index, Collection c) {
		if (mixed) {
			editElement.getPresentation().getSections().addAll(c);
			boolean b = super.addAll(index, c);
			if (b)
				changed = true;
		}
		return editElement.getPresentation().getSections().addAll(index, c);
	}

	public Object set(int index, Object element) {
		if (mixed)
			throw new UnsupportedOperationException();
		return editElement.getPresentation().getSections().set(index, element);
	}

	public void clear() {
		if (mixed)
			throw new UnsupportedOperationException();
		editElement.getPresentation().getSections().clear();
	}

	public void move(int index, Object object) {
		if (mixed) {
			super.move(index, object);
			changed = true;
		} else {
			((EList) editElement.getPresentation().getSections()).move(index,
					object);
		}
	}

	public Object move(int targetIndex, int sourceIndex) {
		if (mixed) {
			Object moved = super.move(targetIndex, sourceIndex);
			changed = true;
			return moved;
		} else {
			return ((EList) editElement.getPresentation().getSections()).move(
					targetIndex, sourceIndex);
		}
	}

	public Object get(int index) {
		if (mixed) {
			return super.get(index);
		} else {
			return editElement.getPresentation().getSections().get(index);
		}
	}

	public int size() {
		if (mixed) {
			return super.size();
		} else {
			return editElement.getPresentation().getSections().size();
		}
	}

	public Iterator iterator() {
		if (mixed) {
			return super.iterator();
		} else {
			return editElement.getPresentation().getSections().iterator();
		}
	}

	public boolean contains(Object object) {
		if (mixed) {
			return super.contains(object);
		}
		return editElement.getPresentation().getSections().contains(object);
	}

	public boolean containsAll(Collection collection) {
		if (mixed)
			return super.containsAll(collection);
		return editElement.getPresentation().getSections().containsAll(
				collection);
	}

	public Object[] toArray() {
		if (mixed) {
			return super.toArray();
		}
		return editElement.getPresentation().getSections().toArray();
	}

	public Object[] toArray(Object[] array) {
		if (mixed)
			return super.toArray(array);
		return editElement.getPresentation().getSections().toArray(array);
	}

	public int indexOf(Object object) {
		if (mixed)
			return super.indexOf(object);
		return editElement.getPresentation().getSections().indexOf(object);
	}

	public int lastIndexOf(Object object) {
		if (mixed)
			return super.lastIndexOf(object);
		return editElement.getPresentation().getSections().lastIndexOf(object);
	}

	public boolean isMixed() {
		return mixed;
	}
}
