/*
 * 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.sshd.git.pgm;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.pgm.CommandCatalog;
import org.eclipse.jgit.pgm.CommandRef;
import org.eclipse.jgit.pgm.Die;
import org.eclipse.jgit.pgm.TextBuiltin;
import org.eclipse.jgit.pgm.internal.CLIText;
import org.eclipse.jgit.pgm.opt.CmdLineParser;
import org.eclipse.jgit.pgm.opt.SubcommandHandler;
import org.eclipse.jgit.util.io.ThrowingPrintWriter;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.ExampleMode;
import org.kohsuke.args4j.Option;

/**
 * TODO Add javadoc
 *
 * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
 */
public class EmbeddedCommandRunner {
    @Option(name = "--help", usage = "usage_displayThisHelpText", aliases = { "-h" })
    private boolean help;

    @Option(name = "--show-stack-trace", usage = "usage_displayThejavaStackTraceOnExceptions")
    private boolean showStackTrace;

    @Option(name = "--git-dir", metaVar = "metaVar_gitDir", usage = "usage_setTheGitRepositoryToOperateOn")
    private String gitdir;

    @Argument(index = 0, metaVar = "metaVar_command", required = true, handler = SubcommandHandler.class)
    private TextBuiltin subcommand;

    @Argument(index = 1, metaVar = "metaVar_arg")
    private List<String> arguments = new ArrayList<>();

    private String rootDir;

    public EmbeddedCommandRunner(String rootDir) {
        this.rootDir = rootDir;
    }

    /**
     * Execute a command.
     *
     * @param argv
     *          the command and its arguments
     * @param in
     *          the input stream, may be null in which case the system input stream will be used
     * @param out
     *          the output stream, may be null in which case the system output stream will be used
     * @param err
     *          the error stream, may be null in which case the system error stream will be used
     * @throws Exception
     *          if an error occurs
     */
    public void execute(final String[] argv, InputStream in, OutputStream out, OutputStream err) throws Exception {
        final CmdLineParser clp = new CmdLineParser(this);
        PrintWriter writer = new PrintWriter(err != null ? err : System.err);
        try {
            clp.parseArgument(argv);
        } catch (CmdLineException e) {
            if (argv.length > 0 && !help) {
                writer.println(MessageFormat.format(CLIText.get().fatalError, e.getMessage()));
                writer.flush();
                throw new Die(true);
            }
        }

        if (argv.length == 0 || help) {
            final String ex = clp.printExample(ExampleMode.ALL, CLIText.get().resourceBundle());
            writer.println("jgit" + ex + " command [ARG ...]"); //$NON-NLS-1$
            if (help) {
                writer.println();
                clp.printUsage(writer, CLIText.get().resourceBundle());
                writer.println();
            } else if (subcommand == null) {
                writer.println();
                writer.println(CLIText.get().mostCommonlyUsedCommandsAre);
                final CommandRef[] common = CommandCatalog.common();
                int width = 0;
                for (final CommandRef c : common) {
                    width = Math.max(width, c.getName().length());
                }
                width += 2;

                for (final CommandRef c : common) {
                    writer.print(' ');
                    writer.print(c.getName());
                    for (int i = c.getName().length(); i < width; i++) {
                        writer.print(' ');
                    }
                    writer.print(CLIText.get().resourceBundle().getString(c.getUsage()));
                    writer.println();
                }
                writer.println();
            }
            writer.flush();
            throw new Die(true);
        }

        gitdir = new File(rootDir, gitdir).getPath();

        final TextBuiltin cmd = subcommand;
//        cmd.ins = in;
//        cmd.outs = out;
//        cmd.errs = err;
//        if (cmd.requiresRepository())
//            cmd.init(openGitDir(gitdir), null);
//        else
//            cmd.init(null, gitdir);
//        try {
//            cmd.execute(arguments.toArray(new String[arguments.size()]));
//        } finally {
//            if (cmd.outw != null)
//                cmd.outw.flush();
//            if (cmd.errw != null)
//                cmd.errw.flush();
//        }
        set(cmd, "ins", in);
        set(cmd, "outs", out);
        set(cmd, "errs", err);
        
        Boolean success = (Boolean) call(cmd, "requiresRepository"); 
        if (success) {
            call(cmd, "init", new Class[] {Repository.class, String.class}, new Object[] {openGitDir(gitdir), gitdir});
        } else {
            call(cmd, "init", new Class[] {Repository.class, String.class}, new Object[] {null, gitdir});
        }
        try {
            cmd.execute(arguments.toArray(new String[arguments.size()]));
        } finally {
            if (get(cmd, "outw") != null) {
                ((ThrowingPrintWriter) get(cmd, "outw")).flush();
            }
            if (get(cmd, "errw") != null) {
                ((ThrowingPrintWriter) get(cmd, "errw")).flush();
            }
        }
    }

    private Object get(Object obj, String name) throws IllegalAccessException, NoSuchFieldException {
        Class<?> clazz = obj.getClass();
        while (clazz != null) {
            try {
                Field field = clazz.getDeclaredField(name);
                field.setAccessible(true);
                return field.get(obj);
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
        }
        throw new NoSuchFieldException(name);
    }

    private void set(Object obj, String name, Object val) throws IllegalAccessException, NoSuchFieldException {
        Class<?> clazz = obj.getClass();
        while (clazz != null) {
            try {
                Field field = clazz.getDeclaredField(name);
                field.setAccessible(true);
                field.set(obj, val);
                return;
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
        }
        throw new NoSuchFieldException(name);
    }

    private Object call(Object obj, String name) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        return call(obj, name, new Class[0], new Object[0]);
    }

    private Object call(Object obj, String name, Class<?>[] types, Object[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<?> clazz = obj.getClass();
        while (clazz != null) {
            try {
                Method method = clazz.getDeclaredMethod(name, types);
                method.setAccessible(true);
                return method.invoke(obj, args);
            } catch (NoSuchMethodException e) {
                clazz = clazz.getSuperclass();
            }
        }
        throw new NoSuchMethodException(name);
    }

    /**
     * Evaluate the {@code --git-dir} option and open the repository.
     *
     * @param gitdir
     *            the {@code --git-dir} option given on the command line. May be
     *            null if it was not supplied.
     * @return the repository to operate on.
     * @throws IOException
     *             the repository cannot be opened.
     */
    protected Repository openGitDir(String gitdir) throws IOException {
        return Git.open(new File(gitdir)).getRepository();
        /*
        RepositoryBuilder rb = new RepositoryBuilder() //
                .setGitDir(gitdir != null ? new File(gitdir) : null) //
                .readEnvironment() //
                .findGitDir();
        if (rb.getGitDir() == null)
            throw new Die(CLIText.get().cantFindGitDirectory);
        return rb.build();
        */
    }
}
