/*
 * ====================================================================
 * 
 * The ObjectStyle Group Software License, Version 1.0
 * 
 * Copyright (c) 2005 The ObjectStyle Group and individual authors of the
 * software. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: 1.
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer. 2. Redistributions in
 * binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution. 3. The end-user documentation
 * included with the redistribution, if any, must include the following
 * acknowlegement: "This product includes software developed by the ObjectStyle
 * Group (http://objectstyle.org/)." Alternately, this acknowlegement may
 * appear in the software itself, if and wherever such third-party
 * acknowlegements normally appear. 4. The names "ObjectStyle Group" and
 * "Cayenne" must not be used to endorse or promote products derived from this
 * software without prior written permission. For written permission, please
 * contact andrus@objectstyle.org. 5. Products derived from this software may
 * not be called "ObjectStyle" nor may "ObjectStyle" appear in their names
 * without prior written permission of the ObjectStyle Group.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * OBJECTSTYLE GROUP OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many individuals
 * on behalf of the ObjectStyle Group. For more information on the ObjectStyle
 * Group, please see <http://objectstyle.org/> .
 *  
 */
package org.objectstyle.wolips.bindings.wod;

import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.objectstyle.wolips.bindings.Activator;
import org.objectstyle.wolips.bindings.api.ApiModelException;
import org.objectstyle.wolips.bindings.api.ApiUtils;
import org.objectstyle.wolips.bindings.api.Binding;
import org.objectstyle.wolips.bindings.api.IApiBinding;
import org.objectstyle.wolips.bindings.api.Validation;
import org.objectstyle.wolips.bindings.api.Wo;
import org.objectstyle.wolips.bindings.preferences.PreferenceConstants;
import org.objectstyle.wolips.bindings.utils.BindingReflectionUtils;

/**
 * @author mschrag
 */
public abstract class AbstractWodElement implements IWodElement, Comparable<IWodElement> {
  private List<IWodBinding> _bindings;

  private boolean _inline;

  private String _tagName;
  
  private boolean _inherited;

  public AbstractWodElement() {
    _bindings = new LinkedList<IWodBinding>();
  }
  
  public void setInherited(boolean inherited) {
		_inherited = inherited;
	}
  
  public boolean isInherited() {
		return _inherited;
	}

  public boolean isInline() {
    return _inline;
  }

  public void setInline(boolean inline) {
    _inline = inline;
  }

  public void addBinding(IWodBinding binding) {
    _bindings.add(binding);
  }
  
  public void removeBinding(IWodBinding binding) {
    _bindings.remove(binding);
  }

  public List<IWodBinding> getBindings() {
    return _bindings;
  }

  public IWodBinding getBindingNamed(String name) {
    IWodBinding matchingBinding = null;
    Iterator<IWodBinding> wodBindingsIter = _bindings.iterator();
    while (matchingBinding == null && wodBindingsIter.hasNext()) {
      IWodBinding wodBinding = wodBindingsIter.next();
      if (name.equals(wodBinding.getName())) {
        matchingBinding = wodBinding;
      }
    }
    return matchingBinding;
  }

  public String getBindingValue(String name) {
    String value = null;
    IWodBinding binding = getBindingNamed(name);
    if (binding != null) {
      value = binding.getValue();
    }
    return value;
  }

  public Map<String, String> getBindingsMap() {
    Map<String, String> bindingsMap = new HashMap<String, String>();
    Iterator<IWodBinding> bindingsIter = _bindings.iterator();
    while (bindingsIter.hasNext()) {
      IWodBinding binding = bindingsIter.next();
      bindingsMap.put(binding.getName(), binding.getValue());
    }
    return bindingsMap;
  }

  public int compareTo(IWodElement otherElement) {
    String otherName = otherElement.getElementName();
    int comparison = getElementName().compareTo(otherName);
    return comparison;
  }

  public void writeWodFormat(Writer writer, boolean alphabetize) throws IOException {
    List<IWodBinding> bindings = getBindings();
    if (alphabetize) {
      bindings = new LinkedList<IWodBinding>(bindings);
      Collections.sort(bindings, new WodBindingComparator());
    }
    writer.write(getElementName());
    writer.write(" : ");
    writer.write(getElementType());
    writer.write(" {");
    writer.write("\n");
    for (IWodBinding binding : bindings) {
      binding.writeWodFormat(writer);
      writer.write("\n");
    }
    writer.write("}\n");
  }

  public void writeInlineFormat(Writer writer, String content, boolean alphabetize, String bindingPrefix, String bindingSuffix) throws IOException {
    writeInlineFormat(writer, content, alphabetize, true, true, true, bindingPrefix, bindingSuffix);
  }

  public void writeInlineFormat(Writer writer, String content, boolean alphabetize, boolean showOpenTag, boolean showContent, boolean showCloseTag, String bindingPrefix, String bindingSuffix) throws IOException {
    List<IWodBinding> bindings = getBindings();
    if (alphabetize) {
      bindings = new LinkedList<IWodBinding>(bindings);
      Collections.sort(bindings, new WodBindingComparator());
    }
    if (showOpenTag) {
      writer.write("<");
      writer.write(getTagName());
      for (IWodBinding binding : bindings) {
        binding.writeInlineFormat(writer, bindingPrefix, bindingSuffix);
      }
      if (content == null) {
        writer.write("/>");
      }
      else {
        writer.write(">");
      }
    }
    if (content != null) {
      if (showContent) {
        writer.write(content);
      }
      if (showCloseTag) {
        writer.write("</");
        writer.write(getTagName());
        writer.write(">");
      }
    }
  }

  public void setTagName(String tagName) {
    _tagName = tagName;
  }

  public String getTagName() {
    String tagName;
    if (_tagName == null) {
      tagName = "wo:" + getElementType();
    }
    else {
      tagName = _tagName;
    }
    return tagName;
  }

  public Wo getApi(IJavaProject javaProject, TypeCache cache) throws JavaModelException, ApiModelException {
    String elementTypeName = getElementType();
    IType elementType = BindingReflectionUtils.findElementType(javaProject, elementTypeName, false, cache);
    Wo wo = ApiUtils.findApiModelWo(elementType, cache.getApiCache(javaProject));
    return wo;
  }

  public IApiBinding[] getApiBindings(Wo api) {
    IApiBinding[] wodBindings = null;
    boolean apiFound = false;
    try {
      if (api != null) {
        apiFound = true;
        List<IApiBinding> visibleBindings = new LinkedList<IApiBinding>();
        List<Binding> apiBindings = api.getBindings();
        visibleBindings.addAll(apiBindings);
        for (IWodBinding wodBinding : getBindings()) {
          String bindingName = wodBinding.getName();
          boolean wodBindingDefinedInApi = false;
          for (IApiBinding apiBinding : apiBindings) {
            if (apiBinding.getName().equals(bindingName)) {
              wodBindingDefinedInApi = true;
              break;
            }
          }
          if (!wodBindingDefinedInApi) {
            visibleBindings.add(wodBinding);
          }
        }
        wodBindings = visibleBindings.toArray(new IApiBinding[visibleBindings.size()]);
      }
    }
    catch (Throwable t) {
      Activator.getDefault().log("Failed to retrieve bindings for " + this + ".", t);
    }
    if (!apiFound) {
      List<IWodBinding> currentBindings = getBindings();
      wodBindings = currentBindings.toArray(new IApiBinding[currentBindings.size()]);
    }
    return wodBindings;
  }

  public abstract int getLineNumber();

  public void fillInProblems(IJavaProject javaProject, IType javaFileType, boolean checkBindingValues, List<WodProblem> problems, TypeCache typeCache, HtmlElementCache htmlCache) throws CoreException {
    String elementTypeName = getElementType();

    String elementName = getElementName();
    int lineNumber = getLineNumber();
  	String wodMissingComponentSeverity = Activator.getDefault().getPluginPreferences().getString(PreferenceConstants.WOD_MISSING_COMPONENT_SEVERITY_KEY);
  	String unusedWodElementSeverity = Activator.getDefault().getPluginPreferences().getString(PreferenceConstants.UNUSED_WOD_ELEMENT_SEVERITY_KEY);
    if (!PreferenceConstants.IGNORE.equals(unusedWodElementSeverity) && !_inline && !htmlCache.containsElementNamed(elementName)) {
      problems.add(new WodElementProblem(this, "There is no element named '" + elementName + "' in your component HTML file", getElementNamePosition(), lineNumber, PreferenceConstants.WARNING.equals(unusedWodElementSeverity)));
    }
    
    String deprecationSeverity = Activator.getDefault().getPluginPreferences().getString(PreferenceConstants.DEPRECATED_BINDING_SEVERITY_KEY);
    if (!PreferenceConstants.IGNORE.equals(deprecationSeverity)) {
      IType elementType = BindingReflectionUtils.findElementType(javaProject, elementTypeName, false, typeCache);
      if (BindingReflectionUtils.memberIsDeprecated(elementType)) {
        problems.add(new WodElementDeprecationProblem(this, "The component named '" + elementTypeName + "' is deprecated.", getElementTypePosition(), lineNumber, PreferenceConstants.WARNING.equals(deprecationSeverity)));
      }
    }

    Wo wo = null;
    if (!PreferenceConstants.IGNORE.equals(wodMissingComponentSeverity)) {
    	IType elementType = BindingReflectionUtils.findElementType(javaProject, elementTypeName, false, typeCache);
	    if (elementType == null || (!elementType.getElementName().equals(elementTypeName) && !elementType.getFullyQualifiedName().equals(elementTypeName))) {
	      problems.add(new WodElementProblem(this, "The class for '" + elementTypeName + "' is either missing or does not extend WOElement.", getElementTypePosition(), lineNumber, PreferenceConstants.WARNING.equals(wodMissingComponentSeverity)));
	    }
	    else {
	    	String wodApiProblemSeverity = Activator.getDefault().getPluginPreferences().getString(PreferenceConstants.WOD_API_PROBLEMS_SEVERITY_KEY);
	    	if (!PreferenceConstants.IGNORE.equals(wodApiProblemSeverity)) {
		      try {
		        wo = ApiUtils.findApiModelWo(elementType, typeCache.getApiCache(javaProject));
		        if (wo != null) {
		          Map<String, String> bindingsMap = getBindingsMap();
		          List<Binding> bindings = wo.getBindings();
		          for (Binding binding : bindings) {
		            String bindingName = binding.getName();
		            if (binding.isExplicitlyRequired() && !bindingsMap.containsKey(bindingName)) {
		              problems.add(new ApiBindingValidationProblem(this, binding, getElementNamePosition(), lineNumber, PreferenceConstants.WARNING.equals(wodApiProblemSeverity)));
		            }
		          }
		          List<Validation> failedValidations = wo.getFailedValidations(bindingsMap);
		          for (Validation failedValidation : failedValidations) {
		            problems.add(new ApiElementValidationProblem(this, failedValidation, getElementNamePosition(), lineNumber, PreferenceConstants.WARNING.equals(wodApiProblemSeverity)));
		          }
		        }
		      }
		      catch (Throwable e) {
		        Activator.getDefault().log(e);
		      }
	    	}
	    }
    }

    Set<String> bindingNames = new HashSet<String>();
    Iterator<IWodBinding> checkForDuplicateBindingsIter = getBindings().iterator();
    while (checkForDuplicateBindingsIter.hasNext()) {
      IWodBinding binding = checkForDuplicateBindingsIter.next();
      String bindingName = binding.getName();
      if (bindingNames.contains(bindingName)) {
        problems.add(new WodBindingNameProblem(this, binding, bindingName, "Duplicate binding named '" + bindingName + "'", binding.getNamePosition(), binding.getLineNumber(), false));
      }
      else {
        bindingNames.add(bindingName);
      }
    }

    JavaModelException javaModelException = null;

    if (checkBindingValues && javaFileType != null) {
      Iterator<IWodBinding> bindingsIter = getBindings().iterator();
      while (bindingsIter.hasNext()) {
        IWodBinding binding = bindingsIter.next();
        try {
          IApiBinding apiBinding = null;
          if (wo != null) {
            apiBinding = wo.getBinding(binding.getName());
          }
          binding.fillInBindingProblems(this, apiBinding, javaProject, javaFileType, problems, typeCache, htmlCache);
        }
        catch (JavaModelException t) {
          javaModelException = t;
          Activator.getDefault().log("Failed to check wod binding values.", t);
        }
        catch (Throwable t) {
          Activator.getDefault().log("Failed to check wod binding values.", t);
        }
      }
    }

    if (javaModelException != null) {
      throw javaModelException;
    }
  }

  public boolean isWithin(IRegion region) {
    return getStartOffset() <= region.getOffset() && getEndOffset() > region.getOffset();
  }

  public boolean isTypeWithin(IRegion region) {
    Position typePosition = getElementTypePosition();
    return typePosition != null && typePosition.getOffset() <= region.getOffset() && typePosition.getOffset() + typePosition.getLength() > region.getOffset();
  }

  @Override
  public String toString() {
    return "[" + getClass().getName() + ": elementName = " + getElementName() + ";  elementType = " + getElementType() + "; bindings = " + _bindings + "]";
  }
}
