/*******************************************************************************
 * Copyright (c) 2002, 2010 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
 *     Jesper Steen Moeller - Added XML Catalogs 1.1 support
 *     
 *******************************************************************************/
package org.eclipse.wst.xml.core.internal.catalog;

import java.io.IOException;
import java.io.InputStream;
import java.util.Stack;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.eclipse.wst.common.uriresolver.internal.URI;
import org.eclipse.wst.common.uriresolver.internal.util.URIHelper;
import org.eclipse.wst.xml.core.internal.Logger;
import org.eclipse.wst.xml.core.internal.XMLCoreMessages;
import org.eclipse.wst.xml.core.internal.catalog.provisional.ICatalogElement;
import org.eclipse.wst.xml.core.internal.catalog.provisional.ICatalogEntry;
import org.eclipse.wst.xml.core.internal.catalog.provisional.IDelegateCatalog;
import org.eclipse.wst.xml.core.internal.catalog.provisional.IRewriteEntry;
import org.eclipse.wst.xml.core.internal.catalog.provisional.ISuffixEntry;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * 
 * 
 */
public final class CatalogReader
{
  public static void read(Catalog xmlCatalog, InputStream input) throws IOException
  {
    try
    {
      SAXParserFactory factory = SAXParserFactory.newInstance();
      factory.setNamespaceAware(true);
      factory.setValidating(false);
      factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);//$NON-NLS-1$
      SAXParser parser = factory.newSAXParser();
      parser.parse(new InputSource(input), new CatalogContentHandler(xmlCatalog));
    }
    catch (ParserConfigurationException e)
    {
      Logger.logException(e);
    }
    catch (SAXException e)
    {
      Logger.logException(e);
    }
  }
  protected static class CatalogContentHandler extends DefaultHandler
  {
    protected Catalog catalog;
    protected Stack baseURIStack = new Stack();

    public CatalogContentHandler(Catalog xmlCatalog)
    {
      this.catalog = xmlCatalog;
      String base = xmlCatalog.getBase();
      if(base == null || base == "") {  //$NON-NLS-1$
    	  base = xmlCatalog.getLocation();
      }
      baseURIStack.push(base);
    }

    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
    {
      // set base attribute xml:base
      String base = attributes.getValue(OASISCatalogConstants.ATTR_BASE); //$NON-NLS-1$
      if (base != null && !base.equals("")) //$NON-NLS-1$
      {
          // since the xml:base attribute can be relative to the enclosing element's effective base, we're maintaining a
          // stack of absolute URIs
		  if (URI.createURI(base).isRelative())
          {
            base = URI.resolveRelativeURI(base, baseURIStack.peek().toString());
          }

          baseURIStack.push(URIHelper.ensureURIProtocolFormat(base));
      } else {
    	  baseURIStack.push(baseURIStack.peek());
      }

	  // processing for backward compatibility start
	  if (localName.equals(CompatabilityConstants.TAG_USER_ENTRY))
      {
        String typeName = attributes.getValue("", CompatabilityConstants.ATT_TYPE); //$NON-NLS-1$
        boolean isSystem = false;
        if (typeName != null)
        {
        	isSystem = (typeName.compareToIgnoreCase("SYSTEM") == 0); //$NON-NLS-1$
        }
        ICatalogEntry catalogEntry = new CatalogEntry(isSystem ? ICatalogEntry.ENTRY_TYPE_SYSTEM : ICatalogEntry.ENTRY_TYPE_PUBLIC);
        catalogEntry.setKey(attributes.getValue("", CompatabilityConstants.ATT_ID)); //$NON-NLS-1$
        String entryUri = attributes.getValue("", CompatabilityConstants.ATT_URI); //$NON-NLS-1$
        
        // For relative URIs, try to resolve them using the corresponding base URI.
        catalogEntry.setURI(resolveRelative(entryUri));

        String webURL = attributes.getValue("", CompatabilityConstants.ATT_WEB_URL); //$NON-NLS-1$
        if (webURL != null)
        {
        	catalogEntry.setAttributeValue(
        			ICatalogEntry.ATTR_WEB_URL, webURL);
        }
		catalog.addCatalogElement(catalogEntry);
		return;
      }
	  //  processing for backward compatibility end
	  ICatalogElement catalogElement = null;

      if (OASISCatalogConstants.TAG_PUBLIC.equals(localName))
      {
    	  // 6.5.3. The public Entry
    	  catalogElement = createEntry(attributes, ICatalogEntry.ENTRY_TYPE_PUBLIC, OASISCatalogConstants.ATTR_PUBLIC_ID);
      }
      else if (OASISCatalogConstants.TAG_SYSTEM.equals(localName))
      {
    	  // 6.5.4. The system Element
    	  catalogElement = createEntry(attributes, ICatalogEntry.ENTRY_TYPE_SYSTEM, OASISCatalogConstants.ATTR_SYSTEM_ID);
      }
      else if (OASISCatalogConstants.TAG_URI.equals(localName))
      {
    	  // 6.5.9. The uri Element
    	  catalogElement = createEntry(attributes, ICatalogEntry.ENTRY_TYPE_URI, OASISCatalogConstants.ATTR_NAME);
      }
      else if (OASISCatalogConstants.TAG_REWRITE_SYSTEM.equals(localName))
      {
    	  // 6.5.5. The rewriteSystem Element
    	  catalogElement = createRewrite(attributes, IRewriteEntry.REWRITE_TYPE_SYSTEM, OASISCatalogConstants.ATTR_SYSTEM_ID_START_STRING);
      }
      else if (OASISCatalogConstants.TAG_REWRITE_URI.equals(localName))
      {
    	  // 6.5.9. The uri Element
    	  catalogElement = createRewrite(attributes, IRewriteEntry.REWRITE_TYPE_URI, OASISCatalogConstants.ATTR_URI_START_STRING);
      }
      else if (OASISCatalogConstants.TAG_DELEGATE_PUBLIC.equals(localName))
      {
    	  // 6.5.7. The delegatePublic Element
    	  catalogElement = createDelegate(attributes, IDelegateCatalog.DELEGATE_TYPE_PUBLIC, OASISCatalogConstants.ATTR_PUBLIC_ID_START_STRING);
      }
      else if (OASISCatalogConstants.TAG_DELEGATE_SYSTEM.equals(localName))
      {
    	  // 6.5.8. The delegateSystem Element
    	  catalogElement = createDelegate(attributes, IDelegateCatalog.DELEGATE_TYPE_SYSTEM, OASISCatalogConstants.ATTR_SYSTEM_ID_START_STRING);
      }
      else if (OASISCatalogConstants.TAG_DELEGATE_URI.equals(localName))
      {
    	  // 6.5.12. The delegateURI Element
    	  catalogElement = createDelegate(attributes, IDelegateCatalog.DELEGATE_TYPE_URI, OASISCatalogConstants.ATTR_URI_START_STRING);
      }
      else if (OASISCatalogConstants.TAG_SYSTEM_SUFFIX.equals(localName))
      {
    	  // 6.5.6. The systemSuffix Element
    	  catalogElement = createSuffix(attributes, ISuffixEntry.SUFFIX_TYPE_SYSTEM, OASISCatalogConstants.ATTR_SYSTEM_ID_SUFFFIX);
      }
      else if (OASISCatalogConstants.TAG_URI_SUFFIX.equals(localName))
      {
    	  // 6.5.11. The uriSuffix Element
    	  catalogElement = createSuffix(attributes, ISuffixEntry.SUFFIX_TYPE_URI, OASISCatalogConstants.ATTR_URI_SUFFIX);
      }
      else if (OASISCatalogConstants.TAG_NEXT_CATALOG.equals(localName))
      {
        catalogElement = createNextCatalog(attributes);
      }
      
      if (catalogElement == null)
      {
    	// do not set the extra information
        return;
      }

	  String attrId = attributes.getValue("", OASISCatalogConstants.ATTR_ID);//$NON-NLS-1$
	  if (attrId != null && ! "".equals(attrId)) catalogElement.setId(attrId);//$NON-NLS-1$
      // process any other attributes
      for (int j = 0; j < attributes.getLength(); j++)
      {
        String attrName = attributes.getLocalName(j);
        if (!attrName.equals(OASISCatalogConstants.ATTR_URI) && !attrName.equals(OASISCatalogConstants.ATTR_NAME) && !attrName.equals(OASISCatalogConstants.ATTR_PUBLIC_ID)
            && !attrName.equals(OASISCatalogConstants.ATTR_SYSTEM_ID) && !attrName.equals(OASISCatalogConstants.ATTR_CATALOG) && !attrName.equals(OASISCatalogConstants.ATTR_ID)
            && !attrName.equals(OASISCatalogConstants.ATTR_BASE))
        {
          String attrValue = attributes.getValue(attrName);
          if (attrValue != null && !attrValue.equals("")) //$NON-NLS-1$
          {
            catalogElement.setAttributeValue(attrName, attrValue);
          }
        }
      }
      catalog.addCatalogElement(catalogElement);

    }

	private ICatalogElement createNextCatalog(Attributes attributes) {
		String location = attributes.getValue("", OASISCatalogConstants.ATTR_CATALOG);    //$NON-NLS-1$
        NextCatalog delegate = new NextCatalog();
        delegate.setBase((String)baseURIStack.peek());
        delegate.setCatalogLocation(location);  
		return delegate;
	}

    private ICatalogEntry createEntry(Attributes attributes, int entryType, String keyAttributeName) {
    	String key = attributes.getValue("", keyAttributeName); //$NON-NLS-1$
    	if (key == null || key.equals("")) //$NON-NLS-1$
    	{
    		Logger.log(Logger.ERROR, XMLCoreMessages.Catalog_entry_key_not_set);
    		return null;
    	}
    	String entryURI = attributes.getValue("", OASISCatalogConstants.ATTR_URI); //$NON-NLS-1$
    	if (entryURI == null || entryURI.equals("")) //$NON-NLS-1$
    	{
    		Logger.log(Logger.ERROR, XMLCoreMessages.Catalog_entry_uri_not_set);
    		return null;
    	}
    	CatalogEntry entry = new CatalogEntry(entryType);
    	entry.setKey(key);
    	entry.setURI(resolveRelative(entryURI));
    	return entry;
    }

    private IRewriteEntry createRewrite(Attributes attributes, int entryType, String prefixStringName) {
    	String startString = attributes.getValue("", prefixStringName); //$NON-NLS-1$
    	if (startString == null || startString.equals("")) //$NON-NLS-1$
    	{
    		Logger.log(Logger.ERROR, XMLCoreMessages.Catalog_rewrite_startString_not_set);
    		return null;
    	}
    	String prefix = attributes.getValue("", OASISCatalogConstants.ATTR_REWRITE_PREFIX); //$NON-NLS-1$
    	if (prefix == null || prefix.equals("")) //$NON-NLS-1$
    	{
    		Logger.log(Logger.ERROR, XMLCoreMessages.Catalog_rewrite_prefix_not_set);
    		return null;
    	}
    	RewriteEntry entry = new RewriteEntry(entryType);
    	entry.setStartString(startString);
    	entry.setRewritePrefix(resolveRelative(prefix));
    	return entry;
    }

    private IDelegateCatalog createDelegate(Attributes attributes, int entryType, String startStringAttrName) {
    	String startString = attributes.getValue("", startStringAttrName); //$NON-NLS-1$
    	if (startString == null || startString.equals("")) //$NON-NLS-1$
    	{
    		Logger.log(Logger.ERROR, XMLCoreMessages.Catalog_delegate_prefix_not_set);
    		return null;
    	}
    	String catalogUri = attributes.getValue("", OASISCatalogConstants.ATTR_CATALOG); //$NON-NLS-1$
    	if (catalogUri == null || catalogUri.equals("")) //$NON-NLS-1$
    	{
    		Logger.log(Logger.ERROR, XMLCoreMessages.Catalog_delegate_prefix_not_set);
    		return null;
    	}
    	DelegateCatalog entry = new DelegateCatalog(entryType);
    	entry.setStartString(startString);
    	entry.setCatalogLocation(catalogUri);
    	return entry;
    }

    private ISuffixEntry createSuffix(Attributes attributes, int entryType, String suffixAttrName) {
    	String suffix = attributes.getValue("", suffixAttrName); //$NON-NLS-1$
    	if (suffix == null || suffix.equals("")) //$NON-NLS-1$
    	{
    		Logger.log(Logger.ERROR, XMLCoreMessages.Catalog_suffix_string_not_set);
    		return null;
    	}
    	String uri = attributes.getValue("", OASISCatalogConstants.ATTR_URI); //$NON-NLS-1$
    	if (uri == null || uri.equals("")) //$NON-NLS-1$
    	{
    		Logger.log(Logger.ERROR, XMLCoreMessages.Catalog_suffix_uri_not_set);
    		return null;
    	}
    	SuffixEntry entry = new SuffixEntry(entryType);
    	entry.setSuffix(suffix);
    	entry.setURI(resolveRelative(uri));
    	return entry;
    }

	private String resolveRelative(String entryURI) 
	{
		if(URI.createURI(entryURI).isRelative()) 
		{
        	entryURI = URI.resolveRelativeURI(entryURI, baseURIStack.peek().toString());
        }
		return URIHelper.ensureURIProtocolFormat(entryURI);
	}

    public void endElement(String uri, String localName, String qName) throws SAXException
    {
      if (baseURIStack.size() > 0)
      {
        baseURIStack.pop();
      }
    }
    
  }  
  
  // for backward compatability
  interface CompatabilityConstants{
	  
	  public static final String TAG_ID_XML_CATALOG_SETTINGS = "XMLCatalogSettings"; //$NON-NLS-1$
	  public static final String TAG_ID_USER_ENTRIES = "UserEntries"; //$NON-NLS-1$
	  public static final String TAG_USER_ENTRY = "UserEntry"; //$NON-NLS-1$
	  public static final String ATT_TYPE = "TYPE"; //$NON-NLS-1$
	  public static final String ATT_ID = "ID"; //$NON-NLS-1$
	  public static final String ATT_URI = "URI"; //$NON-NLS-1$
	  public static final String ATT_WEB_URL = "WEB_URL"; //$NON-NLS-1$
  }
}
