/*******************************************************************************
 * Copyright (c) 2011, 2012 Google, 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:
 * 	   Sergey Prigogin (Google) - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.ui.refactoring.gettersandsetters;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IArrayType;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IPointerType;
import org.eclipse.cdt.core.dom.ast.IQualifierType;
import org.eclipse.cdt.core.dom.ast.IType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPField;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPReferenceType;
import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.SemanticUtil;
import org.eclipse.cdt.internal.ui.refactoring.gettersandsetters.AccessorDescriptor.AccessorKind;

class FieldDescriptor {
	private final IASTName fieldName;
	private final IASTDeclarator fieldDeclarator;
	private final AccessorDescriptor getter;
	private final AccessorDescriptor setter;
	private final AccessorDescriptor[] childNodes;
	private final GetterSetterContext context;

	FieldDescriptor(IASTDeclarator field, GetterSetterContext context) {
		this.fieldName = GetterSetterContext.getDeclaratorName(field);
		this.fieldDeclarator = field;
		this.context = context;
		Set<String> namesToAvoid = getNamesToAvoid();
		String name = GetterSetterNameGenerator.generateGetterName(fieldName, namesToAvoid);
		this.getter = new AccessorDescriptor(AccessorKind.GETTER, name, this);
		name = GetterSetterNameGenerator.generateSetterName(fieldName, namesToAvoid);
		if (!isAssignable(field))
			name = null;
		this.setter = new AccessorDescriptor(AccessorKind.SETTER, name, this);

		List<AccessorDescriptor> children = new ArrayList<>(2);
		if (getter.canBeGenerated()) {
			children.add(getter);
		}
		if (setter.canBeGenerated()) {
			children.add(setter);
		}
		childNodes = children.toArray(new AccessorDescriptor[children.size()]);
	}

	private Set<String> getNamesToAvoid() {
		Set<String> namesToAvoid = new HashSet<>();
		// Add field names.
		for (IASTDeclarator fieldDeclarator : context.existingFields) {
			namesToAvoid.add(String.valueOf(GetterSetterContext.getDeclaratorName(fieldDeclarator).getSimpleID()));
		}
		// Add constructor name.
		if (!context.existingFields.isEmpty()) {
			IASTNode node = context.existingFields.get(0).getParent();
			while (!(node instanceof IASTCompositeTypeSpecifier)) {
				node = node.getParent();
			}
			IASTCompositeTypeSpecifier comp = (IASTCompositeTypeSpecifier) node;
			namesToAvoid.add(String.valueOf(comp.getName().getLastName().getSimpleID()));
		}
		return namesToAvoid;
	}

	private static boolean isAssignable(IASTDeclarator fieldDeclarator) {
		IASTName name = GetterSetterContext.getDeclaratorName(fieldDeclarator);
		IBinding binding = name.resolveBinding();
		if (!(binding instanceof ICPPField))
			return false;
		ICPPField field = (ICPPField) binding;
		IType type = field.getType();
		type = SemanticUtil.getNestedType(type, SemanticUtil.TDEF);
		if (type instanceof IArrayType || type instanceof ICPPReferenceType)
			return false;
		if (type instanceof IPointerType && ((IPointerType) type).isConst())
			return false;
		if (type instanceof IQualifierType && ((IQualifierType) type).isConst())
			return false;
		return true;
	}

	@Override
	public String toString() {
		return fieldName.toString();
	}

	AccessorDescriptor[] getChildNodes() {
		return childNodes;
	}

	boolean missingGetterOrSetter() {
		return getter.canBeGenerated() || setter.canBeGenerated();
	}

	public GetterSetterContext getContext() {
		return context;
	}

	public IASTName getFieldName() {
		return fieldName;
	}

	public IASTDeclarator getFieldDeclarator() {
		return fieldDeclarator;
	}

	public AccessorDescriptor getGetter() {
		return getter;
	}

	public AccessorDescriptor getSetter() {
		return setter;
	}
}
