/*
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package org.apache.flex.swc.catalog;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import javax.xml.stream.StreamFilter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.apache.flex.compiler.common.DependencyType;
import org.apache.flex.compiler.internal.config.QNameNormalization;
import org.apache.flex.compiler.problems.ComponentTagWithoutURIProblem;
import org.apache.flex.compiler.problems.FileInLibraryIOProblem;
import org.apache.flex.swc.ISWCVersion;
import org.apache.flex.swc.SWC;
import org.apache.flex.swc.SWCComponent;
import org.apache.flex.swc.SWCDigest;
import org.apache.flex.swc.SWCLibrary;
import org.apache.flex.swc.SWCScript;
import org.apache.flex.swc.io.SWCReader;
import org.apache.flex.utils.FilenameNormalization;

/**
 * A StAX implementation to parse catalog.xml in a SWC.
 */
public class StAXCatalogReader implements ICatalogXMLConstants
{
    public StAXCatalogReader(final InputStream in, final SWC swc) throws XMLStreamException
    {
        if (swc == null)
            throw new NullPointerException("SWC model can't be null");
        if (in == null)
            throw new NullPointerException("InputStream can't be null.");

        this.swc = swc;
        this.timeStamps = new HashMap<String, String>();
        this.componentIndex = new HashMap<String, SWCComponent>();

        // A filter only keeps start and end XML elements.
        final StreamFilter filter = new StreamFilter()
        {
            @Override
            public boolean accept(XMLStreamReader reader)
            {
                return reader.isStartElement();
            }
        };

        // Configure the XML factory.
        final XMLInputFactory factory = XMLInputFactory.newInstance();
        assert factory != null : "Null XMLInputFactory";

        // Create Stream XML reader.
        final XMLStreamReader xmlStreamReader = factory.createXMLStreamReader(in);
        reader = factory.createFilteredReader(xmlStreamReader, filter);

    }

    private final XMLStreamReader reader;
    private final SWC swc;
    private final Map<String, String> timeStamps;
    private final Map<String, SWCComponent> componentIndex;
    private SWCLibrary currentLibrary;
    private SWCScript currentScript;

    /**
     * Close the StAX parser and the underlying {@code InputStream}.
     */
    public void close() throws IOException
    {
        try
        {
            reader.close();
        }
        catch (XMLStreamException e)
        {
            throw new IOException(e.getMessage());
        }
    }

    public void parse()
    {
        final String containingSWCFile = FilenameNormalization.normalize(swc.getSWCFile().getAbsolutePath());

        try
        {
            while (reader.hasNext())
            {
                final int next = reader.next();
                if (next != XMLStreamConstants.START_ELEMENT)
                    continue;

                final String tagName = reader.getName().getLocalPart();
                if (tagName.equals(TAG_LIBRARY))
                {
                    final String path = reader.getAttributeValue(null, ATTR_PATH);
                    currentLibrary = new SWCLibrary(path);
                    swc.addLibrary(currentLibrary);
                }
                else if (tagName.equals(TAG_COMPONENT))
                {
                    String className = reader.getAttributeValue(null, ATTR_CLASS_NAME);
                    className = QNameNormalization.normalize(className);                    
                    final String componentName = reader.getAttributeValue(null, ATTR_NAME);
                    final String uri = reader.getAttributeValue(null, ATTR_URI);
                    
                    if (uri == null || uri.isEmpty())
                    {
                        swc.addProblem(new ComponentTagWithoutURIProblem(componentName, SWCReader.CATALOG_XML, containingSWCFile));
                        continue;
                    }
                    
                    final String icon = reader.getAttributeValue(null, ATTR_ICON);
                    final String preview = reader.getAttributeValue(null, ATTR_PREVIEW);
                    final SWCComponent component = new SWCComponent();
                    if (className != null)
                        component.setQName(className.intern());
                    if (componentName != null)
                        component.setName(componentName.intern());
                    
                    // uri is null-checked already
                    component.setURI(uri.intern());
                    
                    if (icon != null)
                        component.setIcon(icon.intern());
                    if (preview != null)
                        component.setPreview(preview.intern());
                    swc.addComponent(component);
                    componentIndex.put(className, component);
                }
                else if (tagName.equals(TAG_SCRIPT))
                {
                    String name = reader.getAttributeValue(null, ATTR_NAME);
                    String mod = reader.getAttributeValue(null, ATTR_MOD);
                    String signatureChecksum = reader.getAttributeValue(null, ATTR_SIGNATURE_CHECKSUM);
                    currentScript = new SWCScript();
                    if (name != null)
                        currentScript.setName(name.intern());
                    if (mod != null)
                        currentScript.setLastModified(Long.parseLong(mod));
                    if (signatureChecksum != null)
                        currentScript.setSignatureChecksum(signatureChecksum);
                    currentLibrary.addScript(currentScript);
                    timeStamps.put(name, mod);
                }
                else if (tagName.equals(TAG_FLEX))
                {
                    final String flexVersion = reader.getAttributeValue(null, ATTR_VERSION);
                    final String minSupportedVersion = reader.getAttributeValue(null, ATTR_MINIMUM_SUPPORTED_VERSION);
                    final String flexBuild = reader.getAttributeValue(null, ATTR_BUILD);
                    ISWCVersion swcVersion = swc.getVersion();
                    swcVersion.setFlexVersion(flexVersion);
                    swcVersion.setFlexMinSupportedVersion(minSupportedVersion);
                    swcVersion.setFlexBuild(flexBuild);
                }
                else if (tagName.equals(TAG_SWC))
                {
                    final String swcVersion = reader.getAttributeValue(null, ATTR_VERSION);
                    swc.getVersion().setSWCVersion(swcVersion);
                }
                else if (tagName.equals(TAG_DEF))
                {
                    String id = reader.getAttributeValue(null, ATTR_ID);
                    id = QNameNormalization.normalize(id);
                    currentScript.addDefinition(id);

                    final SWCComponent component = componentIndex.get(id);
                    if (component != null)
                        component.setScript(currentScript);
                }
                else if (tagName.equals(TAG_DEP))
                {
                    String id = reader.getAttributeValue(null, ATTR_ID);
                    id = QNameNormalization.normalize(id);
                    final String type = reader.getAttributeValue(null, ATTR_TYPE);
                    assert type.length() == 1;
                    currentScript.addDependency(id, DependencyType.get(type.charAt(0)));
                }
                else if (tagName.equals(TAG_FILE))
                {
                    String path = reader.getAttributeValue(null, ATTR_PATH);
                    String modString = reader.getAttributeValue(null, ATTR_MOD);

                    long mod = 0;
                    if (modString != null)
                        mod = Long.parseLong(modString);

                    SWCFileEntry fileEntry = new SWCFileEntry(containingSWCFile, path, mod);
                    swc.addFile(fileEntry);
                }
                else if (tagName.equals(TAG_DIGEST))
                {
                    String type = reader.getAttributeValue(null, ATTR_TYPE);
                    String value = reader.getAttributeValue(null, ATTR_VALUE);
                    boolean isSigned = Boolean.parseBoolean(reader.getAttributeValue(null, ATTR_SIGNED));
                    SWCDigest swcDigest = new SWCDigest();
                    swcDigest.setType(type);
                    swcDigest.setValue(value);
                    swcDigest.setSigned(isSigned);
                    
                    currentLibrary.addDigest(swcDigest);
                }
                else if (tagName.equals(TAG_METADATA))
                {
                    String metadataName = reader.getAttributeValue(null, ATTR_NAME);
                    currentLibrary.addNameToKeepAS3MetadataSet(metadataName);
                }
                else if (tagName.equals(TAG_COMPILER))
                {
                    final String compilerName = reader.getAttributeValue(null, ATTR_NAME);
                    final String compilerVersion = reader.getAttributeValue(null, ATTR_VERSION);
                    final String compilerBuild = reader.getAttributeValue(null, ATTR_BUILD);
                    ISWCVersion swcVersion = swc.getVersion();
                    swcVersion.setCompilerName(compilerName);
                    swcVersion.setCompilerVersion(compilerVersion);
                    swcVersion.setCompilerBuild(compilerBuild);
                }
            }
        }
        catch (XMLStreamException e)
        {
            swc.addProblem(new FileInLibraryIOProblem(SWCReader.CATALOG_XML,
                    containingSWCFile,
                    e.getLocalizedMessage()));
        }
    }
    
}
