/*
 *  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.plugins.resolver.util;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.ivy.core.IvyPatternHelper;
import org.apache.ivy.core.module.descriptor.Artifact;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.plugins.repository.Repository;
import org.apache.ivy.plugins.repository.Resource;
import org.apache.ivy.util.Message;

public final class ResolverHelper {
    private ResolverHelper() {
    }

    // lists all the values a token can take in a pattern, as listed by a given url lister
    public static String[] listTokenValues(Repository rep, String pattern, String token) {
        String fileSep = rep.getFileSeparator();
        pattern = rep.standardize(pattern);
        String tokenString = IvyPatternHelper.getTokenString(token);
        int index = pattern.indexOf(tokenString);
        if (index == -1) {
            Message.verbose("unable to list " + token + " in " + pattern
                    + ": token not found in pattern");
            return null;
        }
        if ((pattern.length() <= index + tokenString.length() || fileSep.equals(pattern
                .substring(index + tokenString.length(), index + tokenString.length() + 1)))
                && (index == 0 || fileSep.equals(pattern.substring(index - 1, index)))) {
            // the searched token is a whole name
            String root = pattern.substring(0, index);
            return listAll(rep, root);
        }

        int slashIndex = pattern.substring(0, index).lastIndexOf(fileSep);
        String root = (slashIndex == -1) ? "" : pattern.substring(0, slashIndex);
        try {
            Message.debug("\tusing " + rep + " to list all in " + root);
            String[] all = listAll(rep, root);
            if (all == null) {
                return null;
            }
            Message.debug("\t\tfound " + all.length + " urls");
            List<String> ret = new ArrayList<>(all.length);
            int endNameIndex = pattern.indexOf(fileSep, slashIndex + 1);
            String namePattern;
            if (endNameIndex != -1) {
                namePattern = pattern.substring(slashIndex + 1, endNameIndex);
            } else {
                namePattern = pattern.substring(slashIndex + 1);
            }
            namePattern = namePattern.replaceAll("\\.", "\\\\.");
            namePattern = IvyPatternHelper.substituteToken(namePattern, token, "(.+)");
            Pattern p = Pattern.compile(namePattern);
            for (String path : all) {
                Matcher m = p.matcher(path);
                if (m.matches()) {
                    String value = m.group(1);
                    ret.add(value);
                }
            }
            Message.debug("\t\t" + ret.size() + " matched " + pattern);
            return ret.toArray(new String[ret.size()]);
        } catch (Exception e) {
            Message.warn("problem while listing resources in " + root + " with " + rep, e);
            return null;
        }
    }

    public static String[] listAll(Repository rep, String parent) {
        try {
            String fileSep = rep.getFileSeparator();
            Message.debug("\tusing " + rep + " to list all in " + parent);
            List<String> all = rep.list(parent);
            if (all == null) {
                Message.debug("\t\tno resources found");
                return null;
            }
            Message.debug("\t\tfound " + all.size() + " resources");
            List<String> names = new ArrayList<>(all.size());
            for (String path : all) {
                if (path.endsWith(fileSep)) {
                    path = path.substring(0, path.length() - 1);
                }
                int slashIndex = path.lastIndexOf(fileSep);
                names.add(path.substring(slashIndex + 1));
            }
            return names.toArray(new String[names.size()]);
        } catch (IOException e) {
            Message.verbose("problem while listing resources in " + parent + " with " + rep, e);
            return null;
        } catch (Exception e) {
            Message.warn("problem while listing resources in " + parent + " with " + rep, e);
            return null;
        }
    }

    public static ResolvedResource[] findAll(Repository rep, ModuleRevisionId mrid, String pattern,
            Artifact artifact) {
        // substitute all but revision
        String partiallyResolvedPattern = IvyPatternHelper.substitute(
            pattern,
            ModuleRevisionId.newInstance(mrid,
                IvyPatternHelper.getTokenString(IvyPatternHelper.REVISION_KEY)), artifact);
        Message.debug("\tlisting all in " + partiallyResolvedPattern);

        String[] revs = listTokenValues(rep, partiallyResolvedPattern,
            IvyPatternHelper.REVISION_KEY);
        if (revs != null) {
            Message.debug("\tfound revs: " + Arrays.asList(revs));
            List<ResolvedResource> ret = new ArrayList<>(revs.length);
            for (String rev : revs) {
                String rres = IvyPatternHelper.substituteToken(partiallyResolvedPattern,
                        IvyPatternHelper.REVISION_KEY, rev);
                try {
                    Resource res = rep.getResource(rres);
                    if (res != null) {
                        // we do not test if the resource actually exist here, it would cause
                        // a lot of checks which are not always necessary depending on the usage
                        // which is done of the returned ResolvedResource array
                        ret.add(new ResolvedResource(res, rev));
                    }
                } catch (IOException e) {
                    Message.warn("impossible to get resource from name listed by repository: "
                            + rres, e);
                }
            }
            if (revs.length != ret.size()) {
                Message.debug("\tfound resolved res: " + ret);
            }
            return ret.toArray(new ResolvedResource[ret.size()]);
        }

        if (!partiallyResolvedPattern.contains("[" + IvyPatternHelper.REVISION_KEY + "]")) {
            // the partially resolved pattern is completely resolved, check the resource
            try {
                Resource res = rep.getResource(partiallyResolvedPattern);
                if (res.exists()) {
                    Message.debug("\tonly one resource found without real listing: "
                            + "using and defining it as working@" + rep.getName() + " revision: "
                            + res.getName());
                    return new ResolvedResource[] {new ResolvedResource(res, "working@"
                            + rep.getName())};
                }
            } catch (IOException e) {
                Message.debug("\timpossible to get resource from name listed by repository: "
                        + partiallyResolvedPattern, e);
            }
            Message.debug("\tno revision found");
        }
        return null;
    }

    // public static ResolvedResource[] findAll(Repository rep, ModuleRevisionId mrid, String
    // pattern, Artifact artifact, VersionMatcher versionMatcher, ResourceMDParser mdParser) {
    // // substitute all but revision
    // String partiallyResolvedPattern = IvyPatternHelper.substitute(pattern, new
    // ModuleRevisionId(mrid.getModuleId(),
    // IvyPatternHelper.getTokenString(IvyPatternHelper.REVISION_KEY), mrid.getExtraAttributes()),
    // artifact);
    // Message.debug("\tlisting all in "+partiallyResolvedPattern);
    //
    // String[] revs = listTokenValues(rep, partiallyResolvedPattern,
    // IvyPatternHelper.REVISION_KEY);
    // if (revs != null) {
    // Message.debug("\tfound revs: "+Arrays.asList(revs));
    // List ret = new ArrayList(revs.length);
    // String rres = null;
    // for (int i = 0; i < revs.length; i++) {
    // ModuleRevisionId foundMrid = new ModuleRevisionId(mrid.getModuleId(), revs[i],
    // mrid.getExtraAttributes());
    // if (versionMatcher.accept(mrid, foundMrid)) {
    // rres = IvyPatternHelper.substituteToken(partiallyResolvedPattern,
    // IvyPatternHelper.REVISION_KEY, revs[i]);
    // try {
    // ResolvedResource resolvedResource;
    // if (versionMatcher.needModuleDescriptor(mrid, foundMrid)) {
    // resolvedResource = mdParser.parse(rep.getResource(rres), revs[i]);
    // if (!versionMatcher.accept(mrid,
    // ((MDResolvedResource)resolvedResource).getResolvedModuleRevision().getDescriptor())) {
    // continue;
    // }
    // } else {
    // resolvedResource = new ResolvedResource(rep.getResource(rres), revs[i]);
    // }
    // ret.add(resolvedResource);
    // } catch (IOException e) {
    // Message.warn("impossible to get resource from name listed by repository: "+rres+":
    // "+e.getMessage());
    // }
    // }
    // }
    // if (revs.length != ret.size()) {
    // Message.debug("\tfound resolved res: "+ret);
    // }
    // return (ResolvedResource[])ret.toArray(new ResolvedResource[ret.size()]);
    // } else {
    // // maybe the partially resolved pattern is completely resolved ?
    // try {
    // Resource res = rep.getResource(partiallyResolvedPattern);
    // if (res.exists()) {
    // Message.debug("\tonly one resource found without real listing: using and defining it as
    // working@"+rep.getName()+" revision: "+res.getName());
    // return new ResolvedResource[] {new ResolvedResource(res, "working@"+rep.getName())};
    // }
    // } catch (IOException e) {
    // Message.debug("\timpossible to get resource from name listed by repository:
    // "+partiallyResolvedPattern+": "+e.getMessage());
    // }
    // Message.debug("\tno revision found");
    // }
    // return null;
    // }

    // lists all the values a token can take in a pattern, as listed by a given url lister
    public static String[] listTokenValues(URLLister lister, String pattern, String token) {
        pattern = standardize(pattern);
        if (lister.accept(pattern)) {
            String tokenString = IvyPatternHelper.getTokenString(token);
            int index = pattern.indexOf(tokenString);
            if (index == -1) {
                Message.verbose("unable to list " + token + " in " + pattern
                        + ": token not found in pattern");
                return null;
            }
            if ((pattern.length() <= index + tokenString.length() || "/".equals(pattern
                    .substring(index + tokenString.length(), index + tokenString.length() + 1)))
                    && (index == 0 || "/".equals(pattern.substring(index - 1, index)))) {
                // the searched token is a whole name
                String root = pattern.substring(0, index);
                try {
                    return listAll(lister, new URL(root));
                } catch (MalformedURLException e) {
                    Message.warn("malformed url from pattern root: " + root + ": " + e.getMessage());
                    return null;
                }
            } else {
                int slashIndex = pattern.substring(0, index).lastIndexOf('/');
                String root = slashIndex == -1 ? "" : pattern.substring(0, slashIndex);

                try {
                    Message.debug("\tusing " + lister + " to list all in " + root);
                    List<URL> all = lister.listAll(new URL(root));
                    Message.debug("\t\tfound " + all.size() + " urls");
                    List<String> ret = new ArrayList<>(all.size());
                    int endNameIndex = pattern.indexOf('/', slashIndex + 1);
                    String namePattern;
                    if (endNameIndex != -1) {
                        namePattern = pattern.substring(slashIndex + 1, endNameIndex);
                    } else {
                        namePattern = pattern.substring(slashIndex + 1);
                    }
                    String acceptNamePattern = ".*"
                            + IvyPatternHelper.substituteToken(namePattern, token, "([^/]+)")
                            + ".*";
                    Pattern p = Pattern.compile(acceptNamePattern);
                    for (URL url : all) {
                        String path = standardize(url.getPath());
                        Matcher m = p.matcher(path);
                        if (m.matches()) {
                            String value = m.group(1);
                            ret.add(value);
                        }
                    }
                    Message.debug("\t\t" + ret.size() + " matched " + pattern);
                    return ret.toArray(new String[ret.size()]);
                } catch (Exception e) {
                    Message.warn("problem while listing files in " + root, e);
                    return null;
                }
            }
        }
        return null;
    }

    private static String standardize(String path) {
        return path.replace('\\', '/');
    }

    public static String[] listAll(URLLister lister, URL root) {
        try {
            if (lister.accept(root.toExternalForm())) {
                Message.debug("\tusing " + lister + " to list all in " + root);
                List<URL> all = lister.listAll(root);
                Message.debug("\t\tfound " + all.size() + " urls");
                List<String> names = new ArrayList<>(all.size());
                for (URL dir : all) {
                    String path = dir.getPath();
                    if (path.endsWith("/")) {
                        path = path.substring(0, path.length() - 1);
                    }
                    int slashIndex = path.lastIndexOf('/');
                    names.add(path.substring(slashIndex + 1));
                }
                return names.toArray(new String[names.size()]);
            }
            return null;
        } catch (Exception e) {
            Message.warn("problem while listing directories in " + root, e);
            return null;
        }
    }
}
