package edu.ucsd.arcum.interpreter.fragments;

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.SimpleName;

import edu.ucsd.arcum.exceptions.ArcumError;
import edu.ucsd.arcum.interpreter.query.Entity;
import edu.ucsd.arcum.interpreter.query.EntityType;
import edu.ucsd.arcum.interpreter.query.IEntityLookup;
import edu.ucsd.arcum.interpreter.satisfier.BindingMap;
import edu.ucsd.arcum.util.StringUtil;

// URGENT (!!!) -- Handle matching for the signature case; but will need to think
// of a better place to handle the signature coercion
public class ResolvedEntity extends ProgramFragment
{
    private @Union("Entity") final Object entity;

    public ResolvedEntity(ASTNode astNode) {
        this.entity = astNode;
    }

    public ResolvedEntity(ModifierElement modifierElement) {
        this.entity = modifierElement;
    }

    public ResolvedEntity(SignatureEntity signatureEntity) {
        this.entity = signatureEntity;
    }

    public ResolvedEntity(String string) {
        this.entity = string;
    }

    // Does the program fragment represent the given modifier element?
    public static boolean represents(ProgramFragment frag, ModifierElement mod1) {
        if (frag instanceof ResolvedEntity) {
            ResolvedEntity resolvedEntity = (ResolvedEntity)frag;
            if (resolvedEntity.entity instanceof ModifierElement) {
                ModifierElement mod2 = (ModifierElement)resolvedEntity.entity;
                if (mod1.equals(mod2)) {
                    return true;
                }
            }
        }
        return false;
    }

    // Does the program fragment represent a modifier element that's an access specifier?
    public static boolean representsAccessSpecifier(ProgramFragment frag) {
        if (frag instanceof ResolvedEntity) {
            ResolvedEntity resolvedEntity = (ResolvedEntity)frag;
            if (resolvedEntity.entity instanceof ModifierElement) {
                ModifierElement mod = (ModifierElement)resolvedEntity.entity;
                if (mod.isAccessSpecifier()) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    protected void buildString(StringBuilder buff) {
        buff.append(getIndenter());
        buff.append("(ResolvedEntity ");
        buff.append(Entity.getDisplayString(entity));
        buff.append(")");
    }

    @Override
    public BindingMap generateNode(IEntityLookup lookup, AST ast) {
        if (entity instanceof ASTNode) {
            String id = lookup.lookupEntitiesID(entity);
            return bindNamedRoot(copy(ast, ASTNode.class, (ASTNode)entity), id, EntityType.PUNT);
        }
        else {
            return bindRoot(entity);
        }
    }

    public Object getValue() {
        return entity;
    }

    @Override
    protected BindingMap matchesASTNode(ASTNode node) {
        // MONDAY: Should this be compareTo or compareToWithLocations? And if it
        // should be compareToWithLocations then the unification search can be
        // greatly reduced when the fragment matched contains an instance of
        // ResolvedEntity
        if (Entity.compareToWithLocations(entity, node) == 0) {
            return bindRoot(node);
        }
        else {
            return null;
        }
    }

    @Override
    protected BindingMap matchesModifierElement(ModifierElement modifier) {
        if (Entity.compareTo(entity, modifier) == 0) {
            return bindRoot(modifier);
        }
        else {
            return null;
        }
    }

    @Override
    protected BindingMap matchesSignatureEntity(SignatureEntity signature) {
        ArcumError.fatalError("Unsupported case");
        return null;
    }

    @Override
    final protected BindingMap matchesSimpleProperty(Object node) {
        if (node instanceof String) {
            String text;
            if (entity instanceof SimpleName) {
                SimpleName name = (SimpleName)entity;
                text = name.getIdentifier();
            }
            else if (entity instanceof String) {
                text = (String)entity;
            }
            else {
                text = null;
                ArcumError.fatalError("Cannot handle string case with: %s", StringUtil
                    .debugDisplay(entity));
            }
            if (text.equals(node)) {
                return bindRoot(node);
            }
        }
        return null;
    }

    public static ProgramFragment newInstance(Object entity) {
        ProgramFragment result;
        if (entity instanceof ASTNode) {
            result = new ResolvedEntity((ASTNode)entity);
        }
        else if (entity instanceof ModifierElement) {
            result = new ResolvedEntity((ModifierElement)entity);
        }
        else if (entity instanceof SignatureEntity) {
            result = new ResolvedEntity((SignatureEntity)entity);
        }
        else if (entity instanceof String) {
            result = new ResolvedEntity((String)entity);
        }
        else if (entity instanceof ITypeBinding) {
            result = new ResolvedType((ITypeBinding)entity);
        }
        else if (entity instanceof SubtreeList) {
            result = (SubtreeList)entity;
        }
        else {
            ArcumError.fatalError("Unhandled case: %s", entity);
            return null;
        }
        return result;
    }

    @Override
    public boolean isResolved() {
        return true;
    }

    @Override
    public BindingMap matchResolvedEntity() {
        BindingMap result = bindRoot(entity);
        return result;
    }
}