/*******************************************************************************
 * Copyright (c) 2015 Bruno Medeiros and other Contributors.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Bruno Medeiros - initial API and implementation
 *******************************************************************************/
package melnorme.lang.tooling.structure;

import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue;
import static melnorme.utilbox.core.CoreUtil.areEqual;
import static melnorme.utilbox.misc.StringUtil.prefixStr;

import melnorme.lang.tooling.ElementAttributes;
import melnorme.lang.tooling.ast.SourceRange;
import melnorme.utilbox.collections.ArrayList2;
import melnorme.utilbox.collections.Indexable;
import melnorme.utilbox.misc.HashcodeUtil;

/**
 * Only {@link StructureElement} can extend this class!
 */
abstract class StructureElement_Default extends AbstractStructureContainer implements IStructureElement {
	
	protected final String name;
	protected final SourceRange sourceRange;
	protected final SourceRange nameSourceRange2; // Can be null
	
	protected final StructureElementKind elementKind;
	protected final ElementAttributes elementAttributes;
	
	protected final String type;
	
	protected IStructureElementContainer parent;
	
	public StructureElement_Default(
			String name, 
			SourceRange nameSourceRange, SourceRange sourceRange, 
			StructureElementKind elementKind,
			ElementAttributes elementAttributes, 
			String type, Indexable<StructureElement> children) {
		super(children);
		this.name = assertNotNull(name);
		this.sourceRange = assertNotNull(sourceRange);
		this.nameSourceRange2 = nameSourceRange;
		this.elementKind = assertNotNull(elementKind);
		this.elementAttributes = elementAttributes == null ? new ElementAttributes(null) : elementAttributes;
		this.type = type;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(this == obj) return true;
		if(!(obj instanceof StructureElement)) return false;
		
		StructureElement other = (StructureElement) obj;
		
		return 
			areEqual(name, other.name) &&
			areEqual(nameSourceRange2, other.nameSourceRange2) &&
			areEqual(sourceRange, other.sourceRange) &&
			areEqual(elementKind, other.elementKind) &&
			areEqual(elementAttributes, other.elementAttributes) &&
			areEqual(type, other.type) &&
			areEqual(children, other.children)
			;
	}
	
	@Override
	public int hashCode() {
		// This should be enough to provide a good hash code
		return HashcodeUtil.combinedHashCode(name, sourceRange, elementKind, elementAttributes, children.size());
	}
	
	protected String toStringNode() {
		return "ELEM " + name + sourceRange + " " + elementKind + prefixStr(" : ", type) + " " + elementAttributes;
	}
	
	public StructureElement cloneSubTree() {
		return new StructureElement(name, nameSourceRange2, sourceRange, 
			elementKind, elementAttributes, type, cloneSubTree(children));
	}
	
	public static ArrayList2<StructureElement> cloneSubTree(Indexable<StructureElement> elements) {
		ArrayList2<StructureElement> clonedElements = new ArrayList2<>(elements.size());
		for (StructureElement child : elements) {
			clonedElements.add(child.cloneSubTree());
		}
		return clonedElements;
	}
	
	/* -----------------  ----------------- */
	
	@Override
	public String getName() {
		return name;
	}
	
	@Override
	public SourceRange getSourceRange() {
		return sourceRange;
	}
	
	@Override
	public SourceRange getNameSourceRange2() {
		return nameSourceRange2;
	}
	
	@Override
	public StructureElementKind getKind() {
		return elementKind;
	}
	
	@Override
	public ElementAttributes getAttributes() {
		return elementAttributes;
	}
	
	@Override
	public String getType() {
		return type;
	}
	
	/* -----------------  ----------------- */
	
	@Override
	public IStructureElementContainer getParent() {
		return parent;
	}
	
	@Override
	public void setParent(IStructureElementContainer parent) {
		assertNotNull(parent);
		assertTrue(this.parent == null); // Can only set parent once
		this.parent = parent;
	}
	
//	@Override
//	public String getModuleName() {
//		IStructureElementContainer parent = getParent();
//		return parent == null ? null : parent.getModuleName();
//	}
	
	@Override
	public ISourceFileStructure getContainingFileStructure() {
		return getFileStructure(this);
	}
	
	public static ISourceFileStructure getFileStructure(IStructureElement element) {
		IStructureElementContainer parent = element.getParent();
		
		if(parent instanceof ISourceFileStructure) {
			return (ISourceFileStructure) parent;
		} else if (parent instanceof IStructureElement) {
			return getFileStructure((IStructureElement) parent);
		} else {
			return null;
		}
	}
	
	/* -----------------  ----------------- */
	
	@Override
	public String toString() {
		return toString(true);
	}
	
	public String toString(boolean printChildren) {
		if(printChildren) {
			return new StructureElementPrinter().printElement((StructureElement) this);
		} else {
			return toStringNode();
		}
	}
	
}