package org.zanata.client;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zanata.client.commands.BasicOptions;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.Files;

/**
 * @author Patrick Huang <a
 *         href="mailto:pahuang@redhat.com">pahuang@redhat.com</a>
 */
public class BashCompletionGenerator
{
   private static final Logger log = LoggerFactory.getLogger(BashCompletionGenerator.class);

   private static final Joiner joiner = Joiner.on(" ");

   private List<String> baseCommands;
   private List<Option> genericOptions;
   private Map<String, List<Option>> commandOptions = Maps.newHashMap();
   private Set<Option> allOptions = Sets.newHashSet();

   public static void main(String[] args) throws IOException
   {
      ZanataClient client = new ZanataClient();
      BashCompletionGenerator generator = new BashCompletionGenerator();

      LinkedHashMap<String, BasicOptions> commands = client.getOptionsMap();
      generator.baseCommands = ImmutableList.copyOf(Iterables.transform(commands.values(), new Function<BasicOptions, String>()
      {
         @Override
         public String apply(BasicOptions input)
         {
            return input.getCommandName();
         }
      }));

      generator.genericOptions = getOptions(ZanataClient.class);
      generator.allOptions.addAll(generator.genericOptions);

      for (BasicOptions command : commands.values())
      {
         List<Option> options = getOptions(command.getClass());
         // do we still want generic options to appear?
//         options.removeAll(generator.genericOptions);
         generator.commandOptions.put(command.getCommandName(), options);
         generator.allOptions.addAll(options);
      }

      File to = new File(".", "zanata-cli-completion");
      if (args != null && args.length == 1)
      {
         to = new File(args[0]);
      }
      log.info("writing bash completion file to {}", to);
      generateFile(client, generator, to);
   }

   private static List<Option> getOptions(Class<?> bean)
   {
      ImmutableList.Builder<Option> allOptions = ImmutableList.builder();
      // recursively process all the methods/fields.
      for (Class c = bean; c != null; c = c.getSuperclass())
      {
         ImmutableList.Builder<AccessibleObject> builder = ImmutableList.builder();
         List<AccessibleObject> fieldAndMethods = builder.add(c.getDeclaredFields()).add(c.getDeclaredMethods()).build();
         for (AccessibleObject accessibleObject : fieldAndMethods)
         {
            Option option = accessibleObject.getAnnotation(Option.class);
            if (option != null)
            {
               allOptions.add(option);
            }
         }
      }
      return allOptions.build();
   }

   private static void generateFile(ZanataClient client, BashCompletionGenerator generator, File to) throws IOException
   {
      // if we can use groovy here then here doc will be really handy...
      String commands = joiner.join(generator.baseCommands);
      String genericOptions = optionsToString(generator.genericOptions);

      List<String> lines = Lists.newArrayList();
      lines.add("# ");
      lines.add("# Completion for " + client.getCommandDescription());
      lines.add("# Generated by " + BashCompletionGenerator.class.getSimpleName());
      lines.add("# ");

      lines.add("_zanata()");
      lines.add("{");
      lines.add("    local cur prev opts base cmds");
      lines.add("    COMPREPLY=()");
      lines.add("    cur=\"${COMP_WORDS[COMP_CWORD]}\"");
      lines.add("    prev=\"${COMP_WORDS[COMP_CWORD-1]}\"");
      lines.add("    base=\"${COMP_WORDS[1]}\"");
//      lines.add("    opts=\"" + commands + " " + genericOptions + "\"");
      lines.add("    cmds=\"" + commands + "\"");

      // basic commands as first argument
      lines.add("    if [[ ${#COMP_WORDS[@]} == 2 ]] ; then");
      lines.add("        COMPREPLY=( $(compgen -W \"${cmds} --help\" -- ${cur}) )");
      lines.add("        return 0");
      lines.add("    fi");

      // special treatment for help
      lines.add("    if [[ ${COMP_WORDS[1]} == '--help' ]] ; then");
      lines.add("        COMPREPLY=( $(compgen -W \"${cmds}\" -- ${cur}) )");
      lines.add("        return 0");
      lines.add("    fi");


      // for each special case
      lines.add("    case \"${prev}\" in ");
      // case for file type option
      for (Option option : findByMetaVar(generator, "file"))
      {
         lines.add(String.format("        %s)", option.name()));
         lines.add("            COMPREPLY=( $(compgen -df ${cur}) )");
         lines.add("            return 0");
         lines.add("            ;;");
      }
      // case for directory type option
      for (Option option : findByMetaVar(generator, "dir"))
      {
         lines.add(String.format("        %s)", option.name()));
         lines.add("            COMPREPLY=( $(compgen -f ${cur}) )");
         lines.add("            return 0");
         lines.add("            ;;");
      }
      // case for url
      for (Option option : findByMetaVar(generator, "url"))
      {
         lines.add(String.format("        %s)", option.name()));
         lines.add("            COMPREPLY=( $(compgen -A hostname ${cur}) )");
         lines.add("            return 0");
         lines.add("            ;;");
      }
      lines.add("    esac");

      // for each command
      // TODO should eliminate prev appeared options reappearing
      lines.add("    case \"${base}\" in ");
      // case for individual commands
      for (Map.Entry<String, List<Option>> entry : generator.commandOptions.entrySet())
      {
         String localVar = entry.getKey() + "_opts";
         lines.add(String.format("        %s)", entry.getKey()));
         lines.add(String.format("            local %s=\"%s\"", localVar, optionsToString(entry.getValue())));
         lines.add(String.format("            COMPREPLY=( $(compgen -W \"${%s}\" -- ${cur}) )", localVar));
         lines.add("            return 0");
         lines.add("            ;;");
      }
      lines.add("    esac");

      findByMetaVar(generator, "file");
      lines.add("}");
      lines.add("complete -F _zanata " + client.getCommandName());

      Joiner lineJoiner = Joiner.on(System.getProperty("line.separator"));
      Files.write(lineJoiner.join(lines), to, Charsets.UTF_8);
   }

   private static String optionsToString(Iterable<Option> options)
   {
      return joiner.join(Iterables.transform(options, OptionToName.FUNCTION));
   }

   private static Iterable<Option> findByMetaVar(BashCompletionGenerator generator, final String expectedMetaVar)
   {
      return Iterables.filter(generator.allOptions, new Predicate<Option>()
      {
         @Override
         public boolean apply(Option input)
         {
            // THIS IS NOT QUITE RELIABLE. but hey :)
            return input.metaVar().toLowerCase().contains(expectedMetaVar);
         }
      });
   }

   static enum OptionToName implements Function<Option, String>
   {
      FUNCTION;

      @Override
      public String apply(Option input)
      {
         return input.name();
      }
   }
}

@@ -5486,17 +5486,17 @@
 ompgen -
-f
+d
  $%7Bcur%7D)

fix dir type