/*
 * ====================================================================
 * 
 * The ObjectStyle Group Software License, Version 1.0
 * 
 * Copyright (c) 2005 The ObjectStyle Group and individual authors of the
 * software. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * 3. The end-user documentation included with the redistribution, if any, must
 * include the following acknowlegement: "This product includes software
 * developed by the ObjectStyle Group (http://objectstyle.org/)." Alternately,
 * this acknowlegement may appear in the software itself, if and wherever such
 * third-party acknowlegements normally appear.
 * 
 * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse or
 * promote products derived from this software without prior written permission.
 * For written permission, please contact andrus@objectstyle.org.
 * 
 * 5. Products derived from this software may not be called "ObjectStyle" nor
 * may "ObjectStyle" appear in their names without prior written permission of
 * the ObjectStyle Group.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * OBJECTSTYLE GROUP OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many individuals on
 * behalf of the ObjectStyle Group. For more information on the ObjectStyle
 * Group, please see <http://objectstyle.org/>.
 *  
 */
package org.objectstyle.wolips.ruleeditor.model;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.objectstyle.woenvironment.plist.WOLPropertyListSerialization;

/**
 * This class is the base class to work with d2wmodel files.
 * 
 * @author uli
 * @author <a href="mailto:frederico@moleque.com.br">Frederico Lellis</a>
 * @author <a href="mailto:georg@moleque.com.br">Georg von Bülow</a>
 * @author <a href="mailto:hprange@moleque.com.br">Henrique Prange</a>
 */
public class D2WModel implements PropertyChangeListener {

	private static final int NUMBER_OF_RULES_KEY = 1;

	private static final String RULES_LIST_KEY = "rules";

	boolean hasUnsavedChanges = false;

	private final File modelFile;

	private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

	private final Collection<Rule> rules;

	/**
	 * The D2WModel is created based on a file. An empty file or an existing
	 * d2wmodel file could be passed as a parameter.
	 * 
	 * @param modelFile
	 *            An empty or existing d2wmodel file
	 */
	public D2WModel(File modelFile) {
		if (modelFile == null) {
			throw new IllegalArgumentException("The URL for the d2wmodel file cannot be null");
		}

		this.modelFile = modelFile;

		Map<String, Collection<Map>> modelMap = loadModel();

		rules = modelMapToRules(modelMap);
	}

	public void addPropertyChangeListener(PropertyChangeListener listener) {
		propertyChangeSupport.addPropertyChangeListener(listener);
	}

	public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
		propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
	}

	public void addRule(final Rule newRule) {
		newRule.addPropertyChangeListener(this);

		getRules().add(newRule);

		setHasUnsavedChanges(true);
	}

	/**
	 * Copy the rule received into this D2WModel.
	 * 
	 * @param rule
	 */
	public void copyRule(Rule rule) {
		if (rule == null) {
			return;
		}

		Rule duplicateRule = new Rule(rule);

		addRule(duplicateRule);
	}

	/**
	 * Use this method to create new empty rules for this d2wmodel.
	 * 
	 * @return The newly created rule
	 */
	public Rule createEmptyRule() {
		Rule rule = new Rule();

		addRule(rule);

		return rule;
	}

	public String getModelPath() {
		return modelFile.toString();
	}

	public Collection<Rule> getRules() {
		return rules;
	}

	/**
	 * Tell if the model has changes not saved.
	 * 
	 * @return Returns <code>true</code> if the d2wmodel was changed or
	 *         <code>false</code> otherwise
	 */
	public boolean hasUnsavedChanges() {
		return hasUnsavedChanges;
	}

	protected Map<String, Collection<Map>> loadModel() {
		Map<String, Collection<Map>> modelMap = null;

		try {
			modelMap = (Map<String, Collection<Map>>) WOLPropertyListSerialization.propertyListFromFile(modelFile);
		} catch (Exception exception) {
			throw new IllegalArgumentException("The file " + modelFile + " cannot be found");
		}

		if (modelMap == null) {
			modelMap = new HashMap<String, Collection<Map>>();

			modelMap.put(RULES_LIST_KEY, new ArrayList<Map>());
		}

		return modelMap;

	}

	private Collection<Rule> modelMapToRules(Map<String, Collection<Map>> modelMap) {
		Collection<Map> rulesAsMaps = modelMap.get(RULES_LIST_KEY);

		Collection<Rule> rules = new ArrayList<Rule>(rulesAsMaps.size());

		for (Map ruleAsMap : rulesAsMaps) {
			Rule rule = new Rule(ruleAsMap);

			rule.addPropertyChangeListener(this);

			rules.add(rule);
		}

		return rules;
	}

	public void propertyChange(PropertyChangeEvent event) {
		setHasUnsavedChanges(true);
	}

	public void removePropertyChangeListener(PropertyChangeListener listener) {
		propertyChangeSupport.removePropertyChangeListener(listener);
	}

	public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
		propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
	}

	public void removeRule(final Rule rule) {
		rule.removePropertyChangeListener(this);

		getRules().remove(rule);

		setHasUnsavedChanges(true);
	}

	private Map<String, Collection<Map>> rulesToModelMap() {
		Map<String, Collection<Map>> modelMap = new HashMap<String, Collection<Map>>(NUMBER_OF_RULES_KEY);

		Collection<Map> rulesArray = new ArrayList<Map>();
		List<Rule> rulesList = new ArrayList<Rule>(rules);

		Collections.sort(rulesList, new RuleComparator());

		for (Rule rule : rulesList) {

			Map<String, Object> ruleMap = rule.toMap();

			rulesArray.add(ruleMap);
		}

		modelMap.put(RULES_LIST_KEY, rulesArray);

		return modelMap;
	}

	/**
	 * Stores changes made to this object in the underlying d2wmodel file.
	 */
	public void saveChanges() {
		Map<String, Collection<Map>> modelMap = rulesToModelMap();

		try {
			RuleFileWriter.propertyListToRuleFile(modelFile, modelMap);
			List<Rule> rulesList = new ArrayList<Rule>(rules);
			Collections.sort(rulesList, new RuleComparator());
			RuleFileWriter.writeRuleFileTxt(rulesList, modelFile);

			setHasUnsavedChanges(false);

		} catch (Exception exception) {
			exception.printStackTrace();
		}
	}

	private void setHasUnsavedChanges(boolean newValue) {
		boolean oldValue = hasUnsavedChanges;

		hasUnsavedChanges = newValue;

		propertyChangeSupport.firePropertyChange("HAS_UNSAVED_CHANGES", oldValue, hasUnsavedChanges);
	}
}
