/*
 * JBoss, Home of Professional Open Source
 * Copyright 2013, Red Hat Middleware LLC, and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * Licensed 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.jboss.shrinkwrap.resolver.impl.maven.archive.importer;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchivePath;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.Node;
import org.jboss.shrinkwrap.api.asset.Asset;

/**
 * Represents Hamcrest matchers for validating content of an Archive
 *
 * @author <a href="kpiwko@redhat.com">Karel Piwko</a>
 *
 */
public final class ArchiveContentMatchers {

    public static ArchiveContentMatcher contains(String path) {
        return new ArchiveContentMatcher(path);
    }

    public static ArchiveSizeMatcher size(int filesTotal) {
        return new ArchiveSizeMatcher(filesTotal);
    }

    public static ManifestAssetMatcher hasManifestEntry(String section, String entry, String value) {
        return new ManifestAssetMatcher(section, entry, value);
    }

    public static ManifestAssetMatcher hasManifestEntry(String entry) {
        return new ManifestAssetMatcher(null, entry, null);
    }

    public static ManifestAssetMatcher hasManifestEntry(String entry, String value) {
        return new ManifestAssetMatcher(null, entry, value);
    }

    /**
     * Checks that archive {@link Asset}, that represents Manifest contains a manifest entry with specified value or at least
     * entry itself
     *
     * @author <a href="kpiwko@redhat.com">Karel Piwko</a>
     *
     */
    public static class ManifestAssetMatcher extends BaseMatcher<Asset> implements Matcher<Asset> {

        private final String section;
        private final String name;
        private final String value;

        private ManifestAssetMatcher(String section, String entry, String value) {
            this.section = section;
            this.name = entry;
            this.value = value;
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("Archive manifest contains entry named: ").appendText(name);
            if (value != null) {
                description.appendText(" with value: ").appendText(value);
            }
            if (section != null) {
                description.appendText("(in section ").appendText(section).appendText(")");
            }
        }

        @Override
        public boolean matches(Object item) {
            if (item == null) {
                return false;
            } else if (item instanceof Asset) {
                Manifest mf = getManifest((Asset) item);

                String manifestValue = null;
                if (section == null) {
                    manifestValue = mf.getMainAttributes().getValue(name);
                } else {
                    Attributes attrs = mf.getAttributes(section);
                    if (attrs != null) {
                        manifestValue = attrs.getValue(name);
                    }
                }
                return value == null ? manifestValue != null : value.equals(manifestValue);
            }
            return false;
        }

        private Manifest getManifest(Asset asset) {
            try (InputStream is = asset.openStream()) {
                return new Manifest(is);
            } catch (IOException e) {

            }
            return new Manifest();
        }

    }

    /**
     * Checks that an archive contains specific number of assets (directories are excluded)
     *
     * @author <a href="kpiwko@redhat.com">Karel Piwko</a>
     *
     */
    public static class ArchiveSizeMatcher extends BaseMatcher<Map<ArchivePath, Node>> implements
            Matcher<Map<ArchivePath, Node>> {

        private final int assetsTotal;

        private ArchiveSizeMatcher(int assetsTotal) {
            this.assetsTotal = assetsTotal;
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("Archive contains ").appendText(String.valueOf(assetsTotal)).appendText(" assets.");
        }

        @Override
        public boolean matches(Object item) {
            if (item == null) {
                return false;
            } else if (item instanceof Map<?, ?>) {
                @SuppressWarnings("unchecked")
                Map<ArchivePath, Node> content = (Map<ArchivePath, Node>) item;
                int assets = 0;
                for (Map.Entry<ArchivePath, Node> entry : content.entrySet()) {
                    if (entry.getValue().getAsset() != null) {
                        assets++;
                    }
                }
                return assets == assetsTotal;
            }
            return false;
        }
    }

    /**
     * Checks that archive contains a node under given path
     *
     * @author <a href="kpiwko@redhat.com">Karel Piwko</a>
     *
     */
    public static class ArchiveContentMatcher extends BaseMatcher<Map<ArchivePath, Node>> implements
            Matcher<Map<ArchivePath, Node>> {

        private final String path;

        private ArchiveContentMatcher(String path) {
            this.path = path;
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("Archive contains path ").appendText(path);
        }

        @Override
        public boolean matches(Object item) {
            if (item == null) {
                return false;
            } else if (item instanceof Map<?, ?>) {
                @SuppressWarnings("unchecked")
                Map<ArchivePath, Node> content = (Map<ArchivePath, Node>) item;
                return content.containsKey(ArchivePaths.create(path));
            }

            else if (item instanceof Archive<?>) {
                Archive<?> archive = (Archive<?>) item;
                return archive.contains(path);
            }
            return false;
        }

    }
}
