/*******************************************************************************
 * Copyright (c) 2009, 2016 Red Hat Inc. 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:
 *     Red Hat Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.autotools.core.configure;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.cdt.autotools.core.AutotoolsOptionConstants;
import org.eclipse.cdt.core.CCProjectNature;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.managedbuilder.core.IBuildObjectProperties;
import org.eclipse.cdt.managedbuilder.core.IConfiguration;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;

public class AutotoolsConfiguration implements IAConfiguration {

	public static class Option {
		private String name;
		private String transformedName;
		private String superClassId;
		private int type;
		private String defaultValue;

		public Option(String name, int type) {
			this(name, name, type);
		}

		public Option(String name, String transformedName, int type) {
			this.name = name;
			this.transformedName = transformedName;
			this.type = type;
		}

		public Option(String name, String transformedName, String defaultValue, int type) {
			this.name = name;
			this.transformedName = transformedName;
			this.type = type;
			this.defaultValue = defaultValue;
		}

		public Option(String name, String transformedName, String defaultValue, String superClassId, int type) {
			this.name = name;
			this.transformedName = transformedName;
			this.type = type;
			this.defaultValue = defaultValue;
			this.superClassId = superClassId;
		}

		public String getName() {
			return name;
		}

		public int getType() {
			return type;
		}

		public String getDefaultValue() {
			return defaultValue;
		}

		public String getSuperClassId() {
			return superClassId;
		}

		public String getDescription() {
			return ConfigureMessages.getConfigureDescription(transformedName);
		}

		public String getToolTip() {
			return ConfigureMessages.getConfigureTip(transformedName);
		}
	}

	// Configure options and categories.  List below is ordered.
	// All options following a category are children of that category
	// in a tree view, up to the next category.
	private static Option[] configOpts = new Option[] {
			new Option(AutotoolsOptionConstants.TOOL_CONFIGURE, IConfigureOption.TOOL),
			new Option(AutotoolsOptionConstants.CATEGORY_GENERAL, IConfigureOption.CATEGORY),
			new Option(AutotoolsOptionConstants.OPT_CONFIGDIR, IConfigureOption.INTERNAL),
			new Option(AutotoolsOptionConstants.OPT_CACHE_FILE, "cache_file", IConfigureOption.STRING), //$NON-NLS-1$
			new Option(AutotoolsOptionConstants.OPT_HELP, IConfigureOption.BIN),
			new Option(AutotoolsOptionConstants.OPT_NO_CREATE, "no_create", IConfigureOption.BIN), //$NON-NLS-1$
			new Option(AutotoolsOptionConstants.OPT_QUIET, IConfigureOption.BIN),
			new Option(AutotoolsOptionConstants.OPT_VERSION, IConfigureOption.BIN),
			new Option(AutotoolsOptionConstants.CATEGORY_PLATFORM, IConfigureOption.CATEGORY),
			new Option(AutotoolsOptionConstants.OPT_HOST, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.OPT_BUILD, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.OPT_TARGET, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.CATEGORY_DIRECTORIES, IConfigureOption.CATEGORY),
			new Option(AutotoolsOptionConstants.OPT_PREFIX, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.OPT_EXEC_PREFIX, "exec_prefix", IConfigureOption.STRING), //$NON-NLS-1$
			new Option(AutotoolsOptionConstants.OPT_LIBDIR, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.OPT_BINDIR, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.OPT_SBINDIR, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.OPT_INCLUDEDIR, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.OPT_DATADIR, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.OPT_SYSCONFDIR, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.OPT_INFODIR, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.OPT_MANDIR, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.OPT_SRCDIR, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.OPT_LOCALSTATEDIR, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.OPT_SHAREDSTATEDIR, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.OPT_LIBEXECDIR, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.OPT_OLDINCLUDEDIR, IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.CATEGORY_FILENAMES, IConfigureOption.CATEGORY),
			new Option(AutotoolsOptionConstants.OPT_PROGRAM_PREFIX, "program_prefix", IConfigureOption.STRING), //$NON-NLS-1$
			new Option(AutotoolsOptionConstants.OPT_PROGRAM_SUFFIX, "program_suffix", IConfigureOption.STRING), //$NON-NLS-1$
			new Option(AutotoolsOptionConstants.OPT_PROGRAM_TRANSFORM_NAME, "program_transform_name", //$NON-NLS-1$
					IConfigureOption.STRING),
			new Option(AutotoolsOptionConstants.CATEGORY_ENVVAR, IConfigureOption.CATEGORY),
			new Option(AutotoolsOptionConstants.OPT_ENVVAR, IConfigureOption.ENVVAR),
			new Option(AutotoolsOptionConstants.CATEGORY_FEATURES, IConfigureOption.CATEGORY),
			new Option(AutotoolsOptionConstants.OPT_ENABLE_MAINTAINER_MODE, "enable_maintainer_mode", //$NON-NLS-1$
					IConfigureOption.BIN),
			new Option(AutotoolsOptionConstants.FLAG_CFLAGS, "cflags", AutotoolsOptionConstants.FLAG_CFLAGS_FLAGS, //$NON-NLS-1$
					IConfigureOption.FLAG),
			new Option(AutotoolsOptionConstants.OPT_CFLAGS_DEBUG, "cflags_debug", IConfigureOption.FLAGVALUE), //$NON-NLS-1$ //$NON-NLS-2$
			new Option(AutotoolsOptionConstants.OPT_CFLAGS_GPROF, "cflags_gprof", IConfigureOption.FLAGVALUE), //$NON-NLS-1$ //$NON-NLS-2$
			new Option(AutotoolsOptionConstants.OPT_CFLAGS_GCOV, "cflags_gcov", IConfigureOption.FLAGVALUE), //$NON-NLS-1$ //$NON-NLS-2$
			new Option(AutotoolsOptionConstants.OPT_USER, IConfigureOption.MULTIARG),
			new Option(AutotoolsOptionConstants.TOOL_AUTOGEN, "autogen", "autogen.sh", IConfigureOption.TOOL), //$NON-NLS-1$
			new Option(AutotoolsOptionConstants.CATEGORY_OPTIONS, IConfigureOption.CATEGORY),
			new Option(AutotoolsOptionConstants.OPT_AUTOGENOPTS, IConfigureOption.MULTIARG), };

	private static Option[] toolList;

	private String id;
	private boolean isDirty;
	private boolean isParmsDirty;
	private IProject project;
	private Map<String, IConfigureOption> configOptions;
	private ArrayList<String> configParms = new ArrayList<>();

	public AutotoolsConfiguration(String name) {
		this(null, name, true);
	}

	public AutotoolsConfiguration(IProject project, String name) {
		this(project, name, true);
	}

	private AutotoolsConfiguration(IProject project, String name, boolean initialize) {
		this.id = name;
		this.project = project;
		configOptions = new HashMap<>();
		if (initialize)
			initConfigOptions();
		isParmsDirty = true;
	}

	private void initConfigOptions() {
		// Put configure options in hash map.  Ignore categories.
		ArrayList<Option> tools = new ArrayList<>();
		FlagConfigureOption lastFlag = null;
		IConfiguration configuration = null;
		IBuildObjectProperties buildProperties = null;
		if (project != null) {
			ICConfigurationDescription cfgd = CoreModel.getDefault().getProjectDescription(project)
					.getConfigurationById(id);

			configuration = ManagedBuildManager.getConfigurationForDescription(cfgd);
			if (configuration != null)
				buildProperties = configuration.getBuildProperties();
		}
		for (int i = 0; i < configOpts.length; ++i) {
			Option opt = configOpts[i];
			String defaultValue = opt.getDefaultValue();
			int type = opt.type;
			switch (type) {
			case IConfigureOption.BIN:
				BinConfigureOption b = new BinConfigureOption(opt.name, opt.transformedName, this);
				if (defaultValue != null)
					b.setValue(defaultValue);
				configOptions.put(opt.name, b);
				break;
			case IConfigureOption.STRING:
				StringConfigureOption s = new StringConfigureOption(opt.name, opt.transformedName, this);
				if (defaultValue != null)
					s.setValue(defaultValue);
				configOptions.put(opt.name, s);
				break;
			case IConfigureOption.INTERNAL:
				InternalConfigureOption io = new InternalConfigureOption(opt.name, opt.transformedName, this);
				if (defaultValue != null)
					io.setValue(defaultValue);
				configOptions.put(opt.name, io);
				break;
			case IConfigureOption.MULTIARG:
				MultiArgConfigureOption m = new MultiArgConfigureOption(opt.name, opt.transformedName, this);
				if (buildProperties != null) {
					// Check to see if we have a Debug configuration in which
					// case, default the compiler flags
					// appropriately (for C or C/C++).
					if (opt.name.equals("user")) { //$NON-NLS-1$
						if (buildProperties.containsValue("org.eclipse.cdt.build.core.buildType", //$NON-NLS-1$
								"org.eclipse.linuxtools.cdt.autotools.core.buildType.debug")) { //$NON-NLS-1$
							defaultValue = "CFLAGS='-g -O0'"; //$NON-NLS-1$
							try {
								if (project.hasNature(CCProjectNature.CC_NATURE_ID))
									defaultValue += " CXXFLAGS='-g -O0'"; //$NON-NLS-1$
							} catch (CoreException e) {
								// do nothing
							}
						}
					}
				}
				if (defaultValue != null)
					m.setValue(defaultValue);
				configOptions.put(opt.name, m);
				break;
			case IConfigureOption.TOOL:
				tools.add(opt);
				ConfigureTool t = new ConfigureTool(opt.name, opt.transformedName, this);
				if (defaultValue != null)
					t.setValue(defaultValue);
				configOptions.put(opt.name, t);
				break;
			case IConfigureOption.CATEGORY:
				configOptions.put(opt.name, new ConfigureOptionCategory(opt.name));
				break;
			case IConfigureOption.FLAG:
				FlagConfigureOption f = new FlagConfigureOption(opt.name, opt.transformedName, this);
				if (defaultValue != null)
					f.setValue(defaultValue);
				lastFlag = f;
				configOptions.put(opt.name, f);
				break;
			case IConfigureOption.FLAGVALUE:
				FlagValueConfigureOption fv = new FlagValueConfigureOption(opt.name, opt.transformedName, this,
						ConfigureMessages.getParameter(opt.transformedName));
				if (defaultValue != null)
					fv.setValue(defaultValue);
				lastFlag.addChild(opt.name);
				configOptions.put(opt.name, fv);
				break;
			case IConfigureOption.ENVVAR:
				VariableConfigureOption v = new VariableConfigureOption(opt.name, opt.transformedName, this);
				if (defaultValue != null)
					v.setValue(defaultValue);
				configOptions.put(opt.name, v);
				break;
			}
		}
		toolList = tools.toArray(new Option[tools.size()]);
	}

	public static Option[] getOptionList() {
		return configOpts.clone();
	}

	public static Option[] getChildOptions(String name) {
		ArrayList<Option> options = new ArrayList<>();
		for (int i = 0; i < configOpts.length; ++i) {
			Option opt = configOpts[i];
			if (opt.getName().equals(name)) {
				if (opt.getType() == IConfigureOption.CATEGORY) {
					for (int j = i + 1; j < configOpts.length; ++j) {
						Option o = configOpts[j];
						int type = o.getType();
						if (type != IConfigureOption.CATEGORY && type != IConfigureOption.TOOL)
							options.add(o);
						else
							return options.toArray(new Option[options.size()]);
					}
				} else if (opt.getType() == IConfigureOption.TOOL) {
					for (int j = i + 1; j < configOpts.length; ++j) {
						Option o = configOpts[j];
						int type = o.getType();
						if (type == IConfigureOption.CATEGORY)
							options.add(o);
						else if (type == IConfigureOption.TOOL)
							return options.toArray(new Option[options.size()]);
					}
				}
			}
		}
		return options.toArray(new Option[options.size()]);
	}

	public static Option[] getTools() {
		return toolList.clone();
	}

	@Override
	public IConfigureOption getOption(String name) {
		return configOptions.get(name);
	}

	@Override
	public IAConfiguration copy() {
		return copy(this.id);
	}

	@Override
	public IAConfiguration copy(String newId) {
		AutotoolsConfiguration cfg = new AutotoolsConfiguration(project, newId, false);
		Collection<IConfigureOption> oldValues = configOptions.values();
		for (Iterator<IConfigureOption> i = oldValues.iterator(); i.hasNext();) {
			IConfigureOption opt = i.next();
			cfg.configOptions.put(opt.getName(), opt.copy(cfg));
		}
		if (getId().equals(newId))
			cfg.setDirty(isDirty()); // copying with same id, do not change dirty attribute
		else
			cfg.setDirty(true); // we are cloning with a new id, treat it as never built/dirty
		return cfg;
	}

	@Override
	public String getId() {
		return id;
	}

	@Override
	public boolean isDirty() {
		return isDirty;
	}

	@Override
	public void setDirty(boolean value) {
		isDirty = value;
		if (isDirty)
			isParmsDirty = true;
	}

	@Override
	public Map<String, IConfigureOption> getOptions() {
		return configOptions;
	}

	@Override
	public String getToolParameters(String name) {
		StringBuilder buf = new StringBuilder();
		Option[] options = getChildOptions(name);
		for (int i = 0; i < options.length; ++i) {
			IConfigureOption option = getOption(options[i].getName());
			if (option.getType() == IConfigureOption.CATEGORY) {
				Option[] childOptions = getChildOptions(option.getName());
				for (int j = 0; j < childOptions.length; ++j) {
					IConfigureOption childOption = getOption(childOptions[j].getName());
					String parameter = childOption.getParameter();
					if (!parameter.isEmpty())
						buf.append(' ').append(parameter);
				}
			} else {
				String parameter = option.getParameter();
				if (!parameter.isEmpty())
					buf.append(' ').append(parameter);
			}
		}
		return buf.toString();
	}

	@Override
	public List<String> getToolArgs(String name) {
		if (isParmsDirty) {
			configParms = new ArrayList<>();
			Option[] options = getChildOptions(name);
			for (int i = 0; i < options.length; ++i) {
				IConfigureOption option = getOption(options[i].getName());
				if (option.getType() == IConfigureOption.CATEGORY) {
					Option[] childOptions = getChildOptions(option.getName());
					for (int j = 0; j < childOptions.length; ++j) {
						IConfigureOption childOption = getOption(childOptions[j].getName());
						List<String> parameters = childOption.getParameters();
						configParms.addAll(parameters);
					}
				} else {
					List<String> parameters = option.getParameters();
					configParms.addAll(parameters);
				}
			}
		}
		return configParms;
	}

	@Override
	public void setOption(String name, String value) {
		IConfigureOption option = configOptions.get(name);
		if (option != null) {
			if (!option.getValue().equals(value)) {
				option.setValue(value);
				setDirty(true);
			}
		}
	}

	@Override
	public void setConfigToolDirectory(String configToolDirectory) {
		setOption("configdir", configToolDirectory);
	}

	@Override
	public String getConfigToolDirectory() {
		IConfigureOption option = configOptions.get("configdir");
		return option.getValue();
	}

	@Override
	public void setDefaultOptions() {
		initConfigOptions();
	}
}
