/*******************************************************************************
 * Copyright (c) 2002, 2006 IBM Corporation and others.
 * All rights reserved. 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:
 *     IBM Corporation - initial API and implementation
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 *     
 *******************************************************************************/
package org.eclipse.wst.xml.core.internal.contentmodel.internal.util;

import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.eclipse.wst.xml.core.internal.contentmodel.CMAnyElement;
import org.eclipse.wst.xml.core.internal.contentmodel.CMDocument;
import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMGroup;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNode;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNodeList;
import org.eclipse.wst.xml.core.internal.contentmodel.util.CMVisitor;
import org.eclipse.wst.xml.core.internal.contentmodel.util.DOMNamespaceHelper;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;


/**
 * A special CMValidator that knows about DOMs
 */
public class DOMValidator extends CMValidator
{               
  protected String getNamespaceURI(Node node)
  {   
    return DOMNamespaceHelper.getNamespaceURI(node);
    //return node.getNamespaceURI();
  }
          

  //
  // This is a temporary hack!!
  //
  protected String getFallbackNamepaceURI(CMElementDeclaration ed)
  {   
    String fallbackNamepaceURI = null;
    CMDocument cmDocument = (CMDocument)ed.getProperty("CMDocument"); //$NON-NLS-1$
    if (cmDocument != null)
    {
      fallbackNamepaceURI = (String)cmDocument.getProperty("http://org.eclipse.wst/cm/properties/targetNamespaceURI");   //$NON-NLS-1$
    }  
    return fallbackNamepaceURI;
  }

  /**
   * Encode the Element's NodeList as a List of strings that the validator recognizes
   */
  public List createContentSpecificationList(Element element, CMElementDeclaration ed)
  {                                                                    
    boolean isNamespaceAware = isNamespaceAware(ed);
    Vector v = new Vector();         
    for (Node childNode = element.getFirstChild(); childNode != null; childNode = childNode.getNextSibling())
    { 
      v.add(createContentSpecification(childNode, isNamespaceAware, isNamespaceAware ? getFallbackNamepaceURI(ed) : null));
    }
    return v;
  }


  public List createContentSpecificationList(List nodeList, CMElementDeclaration ed)
  {             
    boolean isNamespaceAware = isNamespaceAware(ed);
    Vector v = new Vector();            
 
    for (Iterator i = nodeList.iterator(); i.hasNext(); )
    {
      Node node = (Node)i.next();
      v.add(createContentSpecification(node, isNamespaceAware, getFallbackNamepaceURI(ed)));
    }
    return v;
  }


  /**
   * Encode the Node as a string that the validator recognizes
   */
  public String createContentSpecification(Node node, boolean isNamespaceAware, String fallbackNamepaceURI)
  {
    String result = "!"; //$NON-NLS-1$
    switch (node.getNodeType())
    {
      case Node.ELEMENT_NODE :
      {  
        String nodeName = node.getNodeName();  
        if (nodeName.startsWith("jsp:")) //$NON-NLS-1$
        {  
          result = "!"; // treat it as a comment so that it's ignored by the validator //$NON-NLS-1$
        }
        else
        {
          if (isNamespaceAware)
          {
            result = DOMNamespaceHelper.getUnprefixedName(nodeName);
            String uri = getNamespaceURI(node);
            if (uri != null)
            {
              result = "[" + uri + "]" + result;    //$NON-NLS-1$ //$NON-NLS-2$
            } 
            else if (fallbackNamepaceURI != null)
            {
              result = "[" + fallbackNamepaceURI + "]" + result;   //$NON-NLS-1$ //$NON-NLS-2$
            }
          }  
          else
          {
            result = nodeName;
          }
        }        
        //ContentModelManager.println("result " + result);
        break;
      }
      case Node.PROCESSING_INSTRUCTION_NODE :
      {
        result = "?"; //$NON-NLS-1$
        break;
      }
      case Node.COMMENT_NODE :
      {
        result = "!"; //$NON-NLS-1$
        break;
      }
      case Node.CDATA_SECTION_NODE :
      {
        result = "\"" + node.getNodeName() + "\""; //$NON-NLS-1$ //$NON-NLS-2$
        break;
      }
      case Node.TEXT_NODE :
      {
        String data = ((Text)node).getData();
        // here we test to see if the test node is 'ignorable'
        if (data != null && data.trim().length() > 0)
        {
          result = "\"" + node.getNodeName() + "\""; //$NON-NLS-1$ //$NON-NLS-2$
        }
        else
        {
          result = "!"; // todo... use another symbol? //$NON-NLS-1$
        }
        break;
      }
    }
    return result;
  }


  /**
   *
   */
  public List createContentSpecificationList(CMNode cmNode)
  {
    List list = new Vector();
    switch (cmNode.getNodeType())
    {
      case CMNode.ELEMENT_DECLARATION :
      {         
        list.add(createContentSpecificationForCMElementDeclaration((CMElementDeclaration)cmNode));   
        break;
      }
      case CMNode.DATA_TYPE :
      {
        list.add("\"" + cmNode.getNodeName() + "\""); //$NON-NLS-1$ //$NON-NLS-2$
        break;
      }
      case CMNode.GROUP :
      {
        createContentSpecificationListForCMGroup((CMGroup)cmNode, list);
        break;
      }
      case CMNode.ANY_ELEMENT :
      {
        list.add("*"); //$NON-NLS-1$
        break;
      }
      default :
      {
        list.add("!"); //$NON-NLS-1$
      }
    }
    return list;
  }
     

  /**
   * 
   */              
  protected String createContentSpecificationForCMElementDeclaration(CMElementDeclaration ed)
  {  
    CMDocument document = (CMDocument)ed.getProperty("CMDocument"); //$NON-NLS-1$
    String uri = document != null ? (String)document.getProperty("http://org.eclipse.wst/cm/properties/targetNamespaceURI") : null; //$NON-NLS-1$
    String string = ed.getNodeName();
    if (uri != null)
    {            
      string = "[" + uri + "]" + string; //$NON-NLS-1$ //$NON-NLS-2$
    }
    return string;
  }
  
  /**
   *
   */
  protected void createContentSpecificationListForCMGroup(CMGroup group, List list)
  {         
    CMGroupContentVisitor visitor = new CMGroupContentVisitor(group, list);
    visitor.visitCMNode(group);
  } 
     
  protected class CMGroupContentVisitor extends CMVisitor
  {  
    protected CMGroup root;                             
    protected List list;

    public CMGroupContentVisitor(CMGroup root, List list)
    {                                        
      this.root = root;        
      this.list = list;
    }

    public void visitCMElementDeclaration(CMElementDeclaration ed)
    {           
      if (ed.getMinOccur() > 0)
      {
        list.add(createContentSpecificationForCMElementDeclaration(ed));
      }
    }       

    public void visitCMAnyElement(CMAnyElement anyElement)
    {   
      list.add("*"); //$NON-NLS-1$
    }

    public void visitCMGroup(CMGroup group)
    {                              
      if (group == root || group.getMinOccur() > 0)
      {
        int op = group.getOperator();
        if (op == CMGroup.SEQUENCE)
        {
          super.visitCMGroup(group);
        }
        else if (op == CMGroup.CHOICE)
        {
          CMNodeList nodeList = group.getChildNodes();
          if (nodeList.getLength() > 0)
          {
            visitCMNode(nodeList.item(0));
          }      
        }
      }
    }
  }

  public boolean isNamespaceAware(CMElementDeclaration ed)
  { 
    return ed != null ? ed.getProperty("http://org.eclipse.wst/cm/properties/isNameSpaceAware") != null : false; //$NON-NLS-1$
  }
     
  /**
   *
   */
  public CMNode[] getOriginArray(CMElementDeclaration ed, Element element)
  {
    ElementPathRecordingResult result = new ElementPathRecordingResult();
    getOriginArray(ed, createContentSpecificationList(element, ed), stringContentComparitor, result);
    return result.getOriginArray();
  }
                   
  /**
   *
   */
  public MatchModelNode getMatchModel(CMElementDeclaration ed, Element element)
  {
    MatchModelNode matchModelNode = null;
    PathRecordingResult result = new PathRecordingResult();
    validate(ed, createContentSpecificationList(element, ed), stringContentComparitor, result);
    if (result.isValid)
    {
      matchModelNode = result.getMatchModel();
    }
    return matchModelNode;
  }
                           

  public List clone(List list)
  {   
    List result = new Vector(list.size());
    result.addAll(list);
    return result;
  }
 
  /**
   *
   */
  public boolean canInsert(CMElementDeclaration ed, List contentSpecificationList, int insertIndex, CMNode cmNode)
  {           
    List clonedList = clone(contentSpecificationList);
    insert(clonedList, insertIndex, cmNode);
    boolean result = isPartiallyValid(ed, clonedList);   
    return result;
  }  

  /**
   *
   */
  public boolean canInsert(CMElementDeclaration ed, List contentSpecificationList, int insertIndex, List cmNodeList)
  {              
    List clonedList = clone(contentSpecificationList);
    insert(clonedList, insertIndex, cmNodeList);
    return isValid(ed, clonedList);
  }  

  /**
   *
   */
  public boolean canRemove(CMElementDeclaration ed, List contentSpecificationList, int startRemoveIndex)
  {
    return canRemove(ed, contentSpecificationList, startRemoveIndex, startRemoveIndex);
  }

  /**
   *
   */
  public boolean canRemove(CMElementDeclaration ed, List contentSpecificationList, int startRemoveIndex, int endRemoveIndex)
  {
    List clonedList = clone(contentSpecificationList);
    remove(clonedList, startRemoveIndex, endRemoveIndex);
    return isValid(ed, clonedList);
  }
                        
  /**
   *
   */
  public boolean canReplace(CMElementDeclaration ed, List contentSpecificationList, int startRemoveIndex, int endRemoveIndex, CMNode cmNode)
  {
    List clonedList = clone(contentSpecificationList);
    remove(clonedList, startRemoveIndex, endRemoveIndex); 
    insert(clonedList, startRemoveIndex, cmNode);
    return isValid(ed, clonedList);
  }

  /**
   *
   */                      
  public boolean isValid(CMElementDeclaration ed, List contentSpecificationList)
  {
    Result result = new Result();
    validate(ed, contentSpecificationList, stringContentComparitor, result);
    return result.isValid;
  }

  public boolean isPartiallyValid(CMElementDeclaration ed, List contentSpecificationList)
  {
    CMValidator.ElementPathRecordingResult result = new CMValidator.ElementPathRecordingResult();
    validate(ed, contentSpecificationList, stringContentComparitor, result);
    int count = getElementCount(contentSpecificationList);
    //System.out.println("elementOriginList " + result.getPartialValidationCount() + "vs" + count);
    return result.getPartialValidationCount() >= count;
  }  
  
  public int getElementCount(List contentSpecificationList)
  {
    int count = 0;
    for (Iterator i = contentSpecificationList.iterator(); i.hasNext(); )
    {
      if (stringContentComparitor.isElement(i.next()))
      {
        count++;
      }  
    }  
    return count;
  }

  protected Result validate(CMElementDeclaration ed, Element element)
  {
    Result result = new Result();
    validate(ed, createContentSpecificationList(element, ed), stringContentComparitor, result);
    return result;
  }


  protected void remove(List stringList, int startRemoveIndex, int endRemoveIndex)
  {
    if (startRemoveIndex != -1)
    {
      for (int i = startRemoveIndex; i <= endRemoveIndex; i++)
      {
        stringList.remove(i);
      }
    }
  }

  protected void insert(List stringList, int insertIndex, CMNode cmNode)
  {
    if (insertIndex != -1)
    {
      stringList.addAll(insertIndex, createContentSpecificationList(cmNode));
    }
  }

  protected void insert(List stringList, int insertIndex, List cmNodeList)
  {
    if (insertIndex != -1)
    {
      int insertListSize = cmNodeList.size();
      for (int i = insertListSize - 1; i >= 0; i--)
      {
        CMNode cmNode = (CMNode)cmNodeList.get(i);
        stringList.addAll(insertIndex, createContentSpecificationList(cmNode));
      }
    }
  }
}
