/*
 * ====================================================================
 * 
 * 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.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

/**
 * @author uli
 * @author <a href="mailto:hprange@moleque.com.br">Henrique Prange</a>
 * @author <a href="mailto:frederico@moleque.com.br">Frederico Lellis</a>
 * @author <a href="mailto:georg@moleque.com.br">Georg von Bülow</a>
 */
public class RightHandSide extends AbstractRuleElement {

	protected static final String DEFAULT_ASSIGNMENT_CLASS_NAME = "com.webobjects.directtoweb.Assignment";

	protected static final String KEY_PATH_KEY = "keyPath";

	protected static final String VALUE_KEY = "value";

	private String keyPath;

	Object value;

	protected RightHandSide() {
		this(new HashMap<String, Object>());

		setAssignmentClassName(DEFAULT_ASSIGNMENT_CLASS_NAME);
		setValue("");
		setKeyPath("");
	}

	protected RightHandSide(final Map properties) {
		super(properties);

		value = properties.get(VALUE_KEY);
		keyPath = (String) properties.get(KEY_PATH_KEY);
	}

	public String getKeyPath() {
		return keyPath;
	}

	public String getValue() {
		return valueForObject(value);
	}

	private Object parseValueString(String value) {
		value = StringUtils.remove(value, "\"");

		// When its an Array
		if (value.startsWith("(")) {
			value = StringUtils.substring(value, 1, value.length() - 1);

			String[] arrayComponents = Pattern.compile(",").split(value);

			ArrayList<Object> array = new ArrayList<Object>();

			for (String expression : arrayComponents) {
				expression = expression.trim();

				if ("".equals(expression)) {
					continue;
				}

				array.add(parseValueString(expression));
			}

			return array;
		}
		// When its a Dictionary
		else if (value.startsWith("{")) {
			Map<String, Object> dictionary = new HashMap<String, Object>();

			value = StringUtils.substring(value, 1, value.lastIndexOf(";"));

			String[] dictionaryComponents = Pattern.compile(";").split(value);

			for (String expression : dictionaryComponents) {
				String key = expression.substring(0, expression.indexOf("=")).trim();

				Object dictionaryValue = parseValueString(expression.substring(expression.indexOf("=") + 1, expression.length()).trim());

				dictionary.put(key, dictionaryValue);
			}
			return dictionary;
		}

		// So its a String
		return value;
	}

	public void setKeyPath(final String keyPath) {
		String oldValue = this.keyPath;

		this.keyPath = keyPath;

		firePropertyChange(KEY_PATH_KEY, oldValue, this.keyPath);
	}

	public void setValue(final String newValue) {
		String oldValue = getValue();

		value = parseValueString(newValue.trim());

		firePropertyChange(VALUE_KEY, oldValue, newValue);
	}

	@Override
	public Map<String, Object> toMap() {
		Map<String, Object> rhsMap = new HashMap<String, Object>();

		rhsMap.put(CLASS_KEY, getAssignmentClassName());
		rhsMap.put(KEY_PATH_KEY, getKeyPath());
		rhsMap.put(VALUE_KEY, value);

		return rhsMap;
	}

	private String valueForObject(Object object) {
		if (object == null) {
			return null;
		}

		if (object instanceof Collection) {
			Collection<Object> arrayOfValues = (Collection<Object>) object;

			if (arrayOfValues.size() == 0) {
				return "()";
			}

			StringBuffer buffer = new StringBuffer();

			buffer.append("( ");

			for (Object value : arrayOfValues) {
				buffer.append(valueForObject(value));
				buffer.append(", ");
			}

			buffer.deleteCharAt(buffer.lastIndexOf(", "));
			buffer.append(")");

			return buffer.toString();
		}

		if (object instanceof Map) {
			Map<Comparable, ?> mapOfValues = (Map<Comparable, ?>) object;

			StringBuffer buffer = new StringBuffer();

			buffer.append("{ ");

			ArrayList<Comparable> list = new ArrayList<Comparable>();

			list.addAll(mapOfValues.keySet());

			Collections.sort(list);

			for (Object key : list) {
				buffer.append("\"");
				buffer.append(key);
				buffer.append("\" = ");
				buffer.append(valueForObject(mapOfValues.get(key)));
				buffer.append("; ");
			}

			buffer.append("}");

			return buffer.toString();
		}

		return "\"" + object.toString() + "\"";
	}
}
