/*
 *  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 grails.init;

import java.lang.reflect.Method;
import java.net.Authenticator;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.stream.Collectors;

import grails.proxy.SystemPropertiesAuthenticator;

/**
 * The purpose of this class is to download the expanded Grails wrapper jars into GRAILS_WRAPPER_HOME (defaults to `.grails` in the user home folder)
 * This class is not meant to be distributed as part of SDKMAN since we'll distribute the expanded jars with it.
 * After downloading the jars, it will delegate to the downloaded grails-cli project.
 * <p>
 * There are 3 ways this class can be used:
 * 1. in testing a grails release (run from a non-project directory) - requires GRAILS_REPO_URL set to `~/.m2/repository`
 * 2. running from a non-project directory (end user usage)
 * 3. running from inside a grails project
 */
public class GrailsWrapper {

    public GrailsWrapper() {
        Authenticator.setDefault(new SystemPropertiesAuthenticator());
    }

    public void execute(String[] args) throws Exception {
        GrailsVersion preferredGrailsVersion = GrailsVersion.getPreferredGrailsVersion();
        LinkedHashSet<GrailsReleaseType> allowedTypes = GrailsVersion.getAllowedReleaseTypes(preferredGrailsVersion, this);

        GrailsUpdater updater = new GrailsUpdater(allowedTypes, preferredGrailsVersion);
        boolean forceUpdate = (args.length > 0 && args[0].trim().equals("update-wrapper"));

        boolean updated = false;
        String[] adjustedArgs = args;
        if (forceUpdate || updater.needsUpdating()) {
            String allowTypesString = allowedTypes.stream().map(GrailsReleaseType::name).collect(Collectors.joining(","));
            System.out.printf("Updating Grails wrapper, allowed versions to update to are [%s]...%n", allowTypesString);

            updated = updater.update();

            // remove "update-wrapper" command argument
            if (forceUpdate) {
                adjustedArgs = Arrays.copyOfRange(args, 1, args.length);
            }
        }

        if (updated) {
            System.out.println("Updated wrapper to version: " + updater.getSelectedVersion().toString());
        }

        URLClassLoader child = new URLClassLoader(new URL[]{updater.getExecutedJarFile().toURI().toURL()});
        Class<?> classToLoad = Class.forName("org.apache.grails.cli.DelegatingShellApplication", true, child);
        Method main = classToLoad.getMethod("main", String[].class);
        main.invoke(null, (Object) adjustedArgs);
    }

    /**
     * @return the version of the packaged wrapper, if not packaged null
     */
    public String getVersion() {
        return Start.class.getPackage().getImplementationVersion();
    }
}
