/*******************************************************************************
 *  Copyright (c) 2003, 2017 IBM Corporation and others.
 *
 *  This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License 2.0
 *  which accompanies this distribution, and is available at
 *  https://www.eclipse.org/legal/epl-2.0/
 *
 *  SPDX-License-Identifier: EPL-2.0
 *
 *  Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.pde.internal.core.text.bundle;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.pde.internal.core.bundle.BundleObject;
import org.eclipse.pde.internal.core.ibundle.IBundleModel;
import org.osgi.framework.BundleException;

public class PDEManifestElement extends BundleObject {

	private static final long serialVersionUID = 1L;

	protected String[] fValueComponents;
	protected TreeMap<String, Serializable> fAttributes;
	protected TreeMap<String, Serializable> fDirectives;

	protected transient ManifestHeader fHeader;

	public PDEManifestElement(ManifestHeader header, String value) {
		setHeader(header);
		setValue(value);
		setModel(fHeader.getBundle().getModel());
	}

	protected PDEManifestElement(ManifestHeader header, ManifestElement manifestElement) {
		setHeader(header);
		init(manifestElement);
		setModel(fHeader.getBundle().getModel());
	}

	public String[] getValueComponents() {
		return fValueComponents;
	}

	protected void setValueComponents(String[] valueComponents) {
		fValueComponents = valueComponents;
	}

	public String[] getAttributes(String key) {
		return getTableValues(fAttributes, key);
	}

	public String getAttribute(String key) {
		return getTableValue(fAttributes, key);
	}

	public Set<String> getKeys() {
		return getTableKeys(fAttributes);
	}

	public void addAttribute(String key, String value) {
		fAttributes = addTableValue(fAttributes, key, value);
	}

	public void setAttribute(String key, String value) {
		fAttributes = setTableValue(fAttributes, key, value);
	}

	public String getDirective(String key) {
		return getTableValue(fDirectives, key);
	}

	public String[] getDirectives(String key) {
		return getTableValues(fDirectives, key);
	}

	public Set<String> getDirectiveKeys() {
		return getTableKeys(fDirectives);
	}

	public void addDirective(String key, String value) {
		fDirectives = addTableValue(fDirectives, key, value);
	}

	public void setDirective(String key, String value) {
		fDirectives = setTableValue(fDirectives, key, value);
	}

	private String getTableValue(TreeMap<String, Serializable> table, String key) {
		if (table == null) {
			return null;
		}
		Object result = table.get(key);
		if (result == null) {
			return null;
		}
		if (result instanceof String) {
			return (String) result;
		}

		@SuppressWarnings({ "unchecked" })
		List<String> valueList = (List<String>) result;
		//return the last value
		return valueList.get(valueList.size() - 1);
	}

	private String[] getTableValues(TreeMap<String, Serializable> table, String key) {
		if (table == null) {
			return null;
		}
		Object result = table.get(key);
		if (result == null) {
			return null;
		}
		if (result instanceof String) {
			return new String[] {(String) result};
		}
		@SuppressWarnings("unchecked")
		List<String> valueList = (List<String>) result;
		return valueList.toArray(new String[valueList.size()]);
	}

	private Set<String> getTableKeys(TreeMap<String, Serializable> table) {
		if (table == null) {
			return null;
		}
		return table.keySet();
	}

	@SuppressWarnings("unchecked")
	private TreeMap<String, Serializable> addTableValue(TreeMap<String, Serializable> table, String key, String value) {
		if (table == null) {
			table = new TreeMap<>();
		}
		Object curValue = table.get(key);
		if (curValue != null) {
			ArrayList<Object> newList;
			// create a list to contain multiple values
			if (curValue instanceof ArrayList) {
				newList = (ArrayList<Object>) curValue;
			} else {
				newList = new ArrayList<>(5);
				newList.add(curValue);
			}
			newList.add(value);
			table.put(key, newList);
		} else {
			table.put(key, value);
		}
		return table;
	}

	private TreeMap<String, Serializable> setTableValue(TreeMap<String, Serializable> table, String key, String value) {
		if (table == null) {
			table = new TreeMap<>();
		}
		if (value == null || value.trim().length() == 0) {
			table.remove(key);
		} else {
			table.put(key, value);
		}
		return table;
	}

	public void setValue(String value) {
		if (value == null) {
			setValueComponents(new String[0]);
			return;
		}
		try {
			ManifestElement[] elements = ManifestElement.parseHeader(fHeader.fName, value);
			if (elements != null && elements.length > 0) {
				init(elements[0]);
			}
		} catch (BundleException e) {
		}
	}

	private void init(ManifestElement manifestElement) {
		setValueComponents(manifestElement.getValueComponents());
		Enumeration<String> attKeys = manifestElement.getKeys();
		if (attKeys != null) {
			while (attKeys.hasMoreElements()) {
				String attKey = attKeys.nextElement();
				String[] values = ManifestElement.getArrayFromList(manifestElement.getAttribute(attKey));
				//empty string in attribute, go with default behavior of attribute
				if (values == null) {
					continue;
				}
				for (String value : values) {
					addAttribute(attKey, value);
				}
			}
		}
		Enumeration<String> dirKeys = manifestElement.getDirectiveKeys();
		if (dirKeys != null) {
			while (dirKeys.hasMoreElements()) {
				String dirKey = dirKeys.nextElement();
				String[] values = ManifestElement.getArrayFromList(manifestElement.getDirective(dirKey));
				for (String value : values) {
					addDirective(dirKey, value);
				}
			}
		}
	}

	public String write() {
		StringBuilder sb = new StringBuilder(getValue());
		appendValuesToBuffer(sb, fAttributes);
		appendValuesToBuffer(sb, fDirectives);
		return sb.toString();
	}

	public String getValue() {
		StringBuilder sb = new StringBuilder();
		if (fValueComponents == null) {
			return ""; //$NON-NLS-1$
		}
		for (int i = 0; i < fValueComponents.length; i++) {
			if (i != 0) {
				sb.append("; "); //$NON-NLS-1$
			}
			sb.append(fValueComponents[i]);
		}
		return sb.toString();
	}

	protected void appendValuesToBuffer(StringBuilder sb, TreeMap<String, Serializable> table) {
		if (table == null) {
			return;
		}
		for (Entry<String, Serializable> entry : table.entrySet()) {
			String dkey = entry.getKey();
			Object value = entry.getValue();
			if (value == null) {
				continue;
			}
			sb.append(";"); //$NON-NLS-1$
			sb.append(dkey);
			sb.append(table.equals(fDirectives) ? ":=" : "="); //$NON-NLS-1$ //$NON-NLS-2$

			if (value instanceof String) {
				boolean wrap = shouldWrap(value.toString());
				if (wrap) {
					sb.append("\""); //$NON-NLS-1$
				}
				sb.append(value);
				if (wrap) {
					sb.append("\""); //$NON-NLS-1$
				}
			} else if (value instanceof ArrayList) {
				ArrayList<?> values = (ArrayList<?>) value;
				boolean wrap = (values.size() > 1 || (values.size() == 1 && shouldWrap(values.get(0).toString())));
				if (wrap) {
					sb.append("\""); //$NON-NLS-1$
				}
				for (int i = 0; i < values.size(); i++) {
					if (i != 0) {
						sb.append(","); //$NON-NLS-1$
					}
					sb.append(values.get(i));
				}
				if (wrap) {
					sb.append("\""); //$NON-NLS-1$
				}
			}
		}
	}

	private boolean shouldWrap(String value) {
		return value.indexOf(' ') != -1 || value.indexOf(',') != -1 || value.indexOf('.') != -1 || value.indexOf('[') != -1 || value.indexOf('(') != -1;
	}

	public ManifestHeader getHeader() {
		return fHeader;
	}

	public void setHeader(ManifestHeader header) {
		fHeader = header;
	}

	public void reconnect(IBundleModel model, ManifestHeader header) {
		super.reconnect(model);
		// Transient Field:  Header
		fHeader = header;
	}

}
