/*
 *  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
 *
 *      https://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.ivy.osgi.obr.xml;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.ivy.osgi.core.BundleArtifact;
import org.apache.ivy.osgi.core.BundleInfo;
import org.apache.ivy.osgi.core.ExecutionEnvironmentProfileProvider;
import org.apache.ivy.osgi.filter.OSGiFilter;
import org.apache.ivy.osgi.filter.OSGiFilterParser;
import org.apache.ivy.osgi.repo.BundleRepoDescriptor;
import org.apache.ivy.osgi.util.DelegatingHandler;
import org.apache.ivy.osgi.util.Version;
import org.apache.ivy.util.Message;
import org.apache.ivy.util.XMLHelper;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class OBRXMLParser {

    public static BundleRepoDescriptor parse(URI baseUri, InputStream in) throws IOException,
            SAXException {
        RepositoryHandler handler = new RepositoryHandler(baseUri);
        try {
            XMLHelper.parse(in, null, handler, null);
        } catch (ParserConfigurationException e) {
            throw new SAXException(e);
        }
        return handler.repo;
    }

    static class RepositoryHandler extends DelegatingHandler {

        static final String REPOSITORY = "repository";

        static final String LASTMODIFIED = "lastmodified";

        static final String NAME = "name";

        BundleRepoDescriptor repo;

        private final URI baseUri;

        public RepositoryHandler(URI baseUri) {
            super(REPOSITORY);
            this.baseUri = baseUri;
            addChild(new ResourceHandler(), new ChildElementHandler<ResourceHandler>() {
                @Override
                public void childHandled(ResourceHandler child) {
                    repo.addBundle(child.bundleInfo);
                }
            });
        }

        @Override
        protected void handleAttributes(Attributes atts) {
            repo = new BundleRepoDescriptor(baseUri,
                    ExecutionEnvironmentProfileProvider.getInstance());

            repo.setName(atts.getValue(NAME));

            repo.setLastModified(atts.getValue(LASTMODIFIED));
        }
    }

    static class ResourceHandler extends DelegatingHandler {

        private static final String DEFAULT_VERSION = "1.0.0";

        static final String RESOURCE = "resource";

        static final String ID = "id";

        static final String PRESENTATION_NAME = "presentationname";

        static final String SYMBOLIC_NAME = "symbolicname";

        static final String URI = "uri";

        static final String VERSION = "version";

        BundleInfo bundleInfo;

        public ResourceHandler() {
            super(RESOURCE);

            setSkipOnError(true); // if anything bad happen in any children, just ignore the
                                  // resource

            addChild(new ResourceSourceHandler(), new ChildElementHandler<ResourceSourceHandler>() {
                @Override
                public void childHandled(ResourceSourceHandler child) {
                    String uri = child.getBufferedChars().trim();
                    if (!uri.endsWith(".jar")) {
                        // the maven plugin is putting some useless source url sometimes...
                        log(Message.MSG_WARN,
                            "A source uri is suspect, it is not ending with .jar, it is probably"
                                    + " a pointer to a download page. Ignoring it.");
                        return;
                    }
                    try {
                        bundleInfo.addArtifact(new BundleArtifact(true, new URI(uri), null));
                    } catch (URISyntaxException e) {
                        log(Message.MSG_WARN, "Incorrect uri " + uri + ". The source of "
                                + bundleInfo.getSymbolicName() + " is then ignored.");
                        return;
                    }
                }
            });
            addChild(new ResourceDescriptionHandler(),
                new ChildElementHandler<ResourceDescriptionHandler>() {
                    @Override
                    public void childHandled(ResourceDescriptionHandler child) {
                        bundleInfo.setDescription(child.getBufferedChars().trim());
                    }
                });
            addChild(new ResourceDocumentationHandler(),
                new ChildElementHandler<ResourceDocumentationHandler>() {
                    @Override
                    public void childHandled(ResourceDocumentationHandler child) {
                        bundleInfo.setDocumentation(child.getBufferedChars().trim());
                    }
                });
            addChild(new ResourceLicenseHandler(),
                new ChildElementHandler<ResourceLicenseHandler>() {
                    @Override
                    public void childHandled(ResourceLicenseHandler child) {
                        bundleInfo.setLicense(child.getBufferedChars().trim());
                    }
                });
            addChild(new ResourceSizeHandler(), new ChildElementHandler<ResourceSizeHandler>() {
                @Override
                public void childHandled(ResourceSizeHandler child) {
                    String size = child.getBufferedChars().trim();
                    try {
                        bundleInfo.setSize(Integer.valueOf(size));
                    } catch (NumberFormatException e) {
                        log(Message.MSG_WARN,
                            "Invalid size for the bundle " + bundleInfo.getSymbolicName() + ": "
                                    + size + ". This size is then ignored.");
                    }
                }
            });
            addChild(new CapabilityHandler(), new ChildElementHandler<CapabilityHandler>() {
                @Override
                public void childHandled(CapabilityHandler child) throws SAXParseException {

                    try {
                        CapabilityAdapter.adapt(bundleInfo, child.capability);
                    } catch (ParseException e) {
                        throw new SAXParseException("Invalid capability: " + e.getMessage(), child
                                .getLocator());
                    }
                }
            });
            addChild(new RequireHandler(), new ChildElementHandler<RequireHandler>() {
                @Override
                public void childHandled(RequireHandler child) throws SAXParseException {
                    try {
                        RequirementAdapter.adapt(bundleInfo, child.requirement);
                    } catch (UnsupportedFilterException e) {
                        throw new SAXParseException("Unsupported requirement filter: "
                                + child.filter + " (" + e.getMessage() + ")", getLocator());
                    } catch (ParseException e) {
                        throw new SAXParseException(
                                "Error in the requirement filter on the bundle: " + e.getMessage(),
                                getLocator());
                    }
                }
            });
            addChild(new ExtendHandler(), new ChildElementHandler<ExtendHandler>() {
                @Override
                public void childHandled(ExtendHandler child) throws SAXParseException {
                    // TODO handle fragment host
                }
            });
        }

        @Override
        protected void handleAttributes(Attributes atts) throws SAXException {
            String symbolicname = atts.getValue(SYMBOLIC_NAME);
            if (symbolicname == null) {
                log(Message.MSG_ERR, "Resource with no symbolic name, skipping it.");
                skip();
                return;
            }

            String v = getOptionalAttribute(atts, VERSION, DEFAULT_VERSION);
            Version version = new Version(v);
            bundleInfo = new BundleInfo(symbolicname, version);
            bundleInfo.setPresentationName(atts.getValue(PRESENTATION_NAME));
            String uri = atts.getValue(URI);
            if (uri != null) {
                try {
                    bundleInfo.addArtifact(new BundleArtifact(false, new URI(uri), null));
                } catch (URISyntaxException e) {
                    log(Message.MSG_ERR, "Incorrect uri " + uri + ". The resource " + symbolicname
                            + " is then ignored.");
                    skip();
                    return;
                }
            }
            bundleInfo.setId(atts.getValue(ID));
        }

        @Override
        protected String getCurrentElementIdentifier() {
            return bundleInfo.getSymbolicName() + "/" + bundleInfo.getVersion();
        }

    }

    static class ResourceSourceHandler extends DelegatingHandler {

        static final String SOURCE = "source";

        public ResourceSourceHandler() {
            super(SOURCE);
            setBufferingChar(true);
        }

    }

    static class ResourceDescriptionHandler extends DelegatingHandler {

        static final String DESCRIPTION = "description";

        public ResourceDescriptionHandler() {
            super(DESCRIPTION);
            setBufferingChar(true);
        }

    }

    static class ResourceDocumentationHandler extends DelegatingHandler {

        static final String DOCUMENTATION = "documentation";

        public ResourceDocumentationHandler() {
            super(DOCUMENTATION);
            setBufferingChar(true);
        }
    }

    static class ResourceLicenseHandler extends DelegatingHandler {

        static final String LICENSE = "license";

        public ResourceLicenseHandler() {
            super(LICENSE);
            setBufferingChar(true);
        }

    }

    static class ResourceSizeHandler extends DelegatingHandler {

        static final String SIZE = "size";

        public ResourceSizeHandler() {
            super(SIZE);
            setBufferingChar(true);
        }
    }

    static class CapabilityHandler extends DelegatingHandler {

        static final String CAPABILITY = "capability";

        static final String NAME = "name";

        Capability capability;

        public CapabilityHandler() {
            super(CAPABILITY);
            addChild(new CapabilityPropertyHandler(),
                new ChildElementHandler<CapabilityPropertyHandler>() {
                    @Override
                    public void childHandled(CapabilityPropertyHandler child) {
                        String name = child.name;
                        String value = child.value;
                        String type = child.type;

                        capability.addProperty(name, value, type);
                    }
                });
        }

        @Override
        protected void handleAttributes(Attributes atts) throws SAXException {
            String name = getRequiredAttribute(atts, NAME);
            capability = new Capability(name);
        }

    }

    static class CapabilityPropertyHandler extends DelegatingHandler {

        static final String CAPABILITY_PROPERTY = "p";

        static final String NAME = "n";

        static final String VALUE = "v";

        static final String TYPE = "t";

        String name;

        String value;

        String type;

        public CapabilityPropertyHandler() {
            super(CAPABILITY_PROPERTY);
        }

        @Override
        protected void handleAttributes(Attributes atts) throws SAXException {
            name = getRequiredAttribute(atts, NAME);
            value = getRequiredAttribute(atts, VALUE);
            type = atts.getValue(TYPE);
        }
    }

    static class AbstractRequirementHandler extends DelegatingHandler {

        static final String NAME = "name";

        static final String OPTIONAL = "optional";

        static final String MULTIPLE = "multiple";

        static final String FILTER = "filter";

        Requirement requirement;

        OSGiFilter filter;

        public AbstractRequirementHandler(String name) {
            super(name);
        }

        @Override
        protected void handleAttributes(Attributes atts) throws SAXException {
            String name = getRequiredAttribute(atts, NAME);

            String filterText = atts.getValue(FILTER);
            filter = null;
            if (filterText != null) {
                try {
                    filter = OSGiFilterParser.parse(filterText);
                } catch (ParseException e) {
                    throw new SAXParseException("Requirement with ill-formed filter: " + filterText,
                            getLocator());
                }
            }

            Boolean optional = getOptionalBooleanAttribute(atts, OPTIONAL, null);
            Boolean multiple = getOptionalBooleanAttribute(atts, MULTIPLE, null);

            requirement = new Requirement(name, filter);
            if (optional != null) {
                requirement.setOptional(optional);
            }
            if (multiple != null) {
                requirement.setMultiple(multiple);
            }
        }

    }

    static class RequireHandler extends AbstractRequirementHandler {

        static final String REQUIRE = "require";

        public RequireHandler() {
            super(REQUIRE);
        }

    }

    static class ExtendHandler extends AbstractRequirementHandler {

        static final String EXTEND = "extend";

        public ExtendHandler() {
            super(EXTEND);
        }

    }
}