package joptsimple;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import joptsimple.internal.AbbreviationMap;
import joptsimple.util.KeyValuePair;

public class OptionParser {
   private final AbbreviationMap<AbstractOptionSpec<?>> recognizedOptions;
   private final Map<Collection<String>, Set<OptionSpec<?>>> requiredIf;
   private OptionParserState state;
   private boolean posixlyCorrect;
   private boolean allowsUnrecognizedOptions;
   private HelpFormatter helpFormatter;

   public OptionParser() {
      this.helpFormatter = new BuiltinHelpFormatter();
      this.recognizedOptions = new AbbreviationMap();
      this.requiredIf = new HashMap();
      this.state = OptionParserState.moreOptions(false);
      this.recognize(new NonOptionArgumentSpec());
   }

   public OptionParser(String optionSpecification) {
      this();
      (new OptionSpecTokenizer(optionSpecification)).configure(this);
   }

   public OptionSpecBuilder accepts(String option) {
      return this.acceptsAll(Collections.singletonList(option));
   }

   public OptionSpecBuilder accepts(String option, String description) {
      return this.acceptsAll(Collections.singletonList(option), description);
   }

   public OptionSpecBuilder acceptsAll(Collection<String> options) {
      return this.acceptsAll(options, "");
   }

   public OptionSpecBuilder acceptsAll(Collection<String> options, String description) {
      if (options.isEmpty()) {
         throw new IllegalArgumentException("need at least one option");
      } else {
         ParserRules.ensureLegalOptions(options);
         return new OptionSpecBuilder(this, options, description);
      }
   }

   public NonOptionArgumentSpec<String> nonOptions() {
      NonOptionArgumentSpec<String> spec = new NonOptionArgumentSpec();
      this.recognize(spec);
      return spec;
   }

   public NonOptionArgumentSpec<String> nonOptions(String description) {
      NonOptionArgumentSpec<String> spec = new NonOptionArgumentSpec(description);
      this.recognize(spec);
      return spec;
   }

   public void posixlyCorrect(boolean setting) {
      this.posixlyCorrect = setting;
      this.state = OptionParserState.moreOptions(setting);
   }

   boolean posixlyCorrect() {
      return this.posixlyCorrect;
   }

   public void allowsUnrecognizedOptions() {
      this.allowsUnrecognizedOptions = true;
   }

   boolean doesAllowsUnrecognizedOptions() {
      return this.allowsUnrecognizedOptions;
   }

   public void recognizeAlternativeLongOptions(boolean recognize) {
      if (recognize) {
         this.recognize(new AlternativeLongOptionSpec());
      } else {
         this.recognizedOptions.remove(String.valueOf("W"));
      }

   }

   void recognize(AbstractOptionSpec<?> spec) {
      this.recognizedOptions.putAll(spec.options(), spec);
   }

   public void printHelpOn(OutputStream sink) throws IOException {
      this.printHelpOn((Writer)(new OutputStreamWriter(sink)));
   }

   public void printHelpOn(Writer sink) throws IOException {
      sink.write(this.helpFormatter.format(this.recognizedOptions.toJavaUtilMap()));
      sink.flush();
   }

   public void formatHelpWith(HelpFormatter formatter) {
      if (formatter == null) {
         throw new NullPointerException();
      } else {
         this.helpFormatter = formatter;
      }
   }

   public OptionSet parse(String... arguments) {
      ArgumentList argumentList = new ArgumentList(arguments);
      OptionSet detected = new OptionSet(this.defaultValues());
      detected.add((AbstractOptionSpec)this.recognizedOptions.get("[arguments]"));

      while(argumentList.hasMore()) {
         this.state.handleArgument(this, argumentList, detected);
      }

      this.reset();
      this.ensureRequiredOptions(detected);
      return detected;
   }

   private void ensureRequiredOptions(OptionSet options) {
      Collection<String> missingRequiredOptions = this.missingRequiredOptions(options);
      boolean helpOptionPresent = this.isHelpOptionPresent(options);
      if (!missingRequiredOptions.isEmpty() && !helpOptionPresent) {
         throw new MissingRequiredOptionException(missingRequiredOptions);
      }
   }

   private Collection<String> missingRequiredOptions(OptionSet options) {
      Collection<String> missingRequiredOptions = new HashSet();
      Iterator i$ = this.recognizedOptions.toJavaUtilMap().values().iterator();

      while(i$.hasNext()) {
         AbstractOptionSpec<?> each = (AbstractOptionSpec)i$.next();
         if (each.isRequired() && !options.has((OptionSpec)each)) {
            missingRequiredOptions.addAll(each.options());
         }
      }

      i$ = this.requiredIf.entrySet().iterator();

      while(i$.hasNext()) {
         Entry<Collection<String>, Set<OptionSpec<?>>> eachEntry = (Entry)i$.next();
         AbstractOptionSpec<?> required = this.specFor((String)((Collection)eachEntry.getKey()).iterator().next());
         if (this.optionsHasAnyOf(options, (Collection)eachEntry.getValue()) && !options.has((OptionSpec)required)) {
            missingRequiredOptions.addAll(required.options());
         }
      }

      return missingRequiredOptions;
   }

   private boolean optionsHasAnyOf(OptionSet options, Collection<OptionSpec<?>> specs) {
      Iterator i$ = specs.iterator();

      OptionSpec each;
      do {
         if (!i$.hasNext()) {
            return false;
         }

         each = (OptionSpec)i$.next();
      } while(!options.has(each));

      return true;
   }

   private boolean isHelpOptionPresent(OptionSet options) {
      boolean helpOptionPresent = false;
      Iterator i$ = this.recognizedOptions.toJavaUtilMap().values().iterator();

      while(i$.hasNext()) {
         AbstractOptionSpec<?> each = (AbstractOptionSpec)i$.next();
         if (each.isForHelp() && options.has((OptionSpec)each)) {
            helpOptionPresent = true;
            break;
         }
      }

      return helpOptionPresent;
   }

   void handleLongOptionToken(String candidate, ArgumentList arguments, OptionSet detected) {
      KeyValuePair optionAndArgument = parseLongOptionWithArgument(candidate);
      if (!this.isRecognized(optionAndArgument.key)) {
         throw OptionException.unrecognizedOption(optionAndArgument.key);
      } else {
         AbstractOptionSpec<?> optionSpec = this.specFor(optionAndArgument.key);
         optionSpec.handleOption(this, arguments, detected, optionAndArgument.value);
      }
   }

   void handleShortOptionToken(String candidate, ArgumentList arguments, OptionSet detected) {
      KeyValuePair optionAndArgument = parseShortOptionWithArgument(candidate);
      if (this.isRecognized(optionAndArgument.key)) {
         this.specFor(optionAndArgument.key).handleOption(this, arguments, detected, optionAndArgument.value);
      } else {
         this.handleShortOptionCluster(candidate, arguments, detected);
      }

   }

   private void handleShortOptionCluster(String candidate, ArgumentList arguments, OptionSet detected) {
      char[] options = extractShortOptionsFrom(candidate);
      this.validateOptionCharacters(options);

      for(int i = 0; i < options.length; ++i) {
         AbstractOptionSpec<?> optionSpec = this.specFor(options[i]);
         if (optionSpec.acceptsArguments() && options.length > i + 1) {
            String detectedArgument = String.valueOf(options, i + 1, options.length - 1 - i);
            optionSpec.handleOption(this, arguments, detected, detectedArgument);
            break;
         }

         optionSpec.handleOption(this, arguments, detected, (String)null);
      }

   }

   void handleNonOptionArgument(String candidate, ArgumentList arguments, OptionSet detectedOptions) {
      this.specFor("[arguments]").handleOption(this, arguments, detectedOptions, candidate);
   }

   void noMoreOptions() {
      this.state = OptionParserState.noMoreOptions();
   }

   boolean looksLikeAnOption(String argument) {
      return ParserRules.isShortOptionToken(argument) || ParserRules.isLongOptionToken(argument);
   }

   boolean isRecognized(String option) {
      return this.recognizedOptions.contains(option);
   }

   void requiredIf(Collection<String> precedentSynonyms, String required) {
      this.requiredIf(precedentSynonyms, (OptionSpec)this.specFor(required));
   }

   void requiredIf(Collection<String> precedentSynonyms, OptionSpec<?> required) {
      Iterator i$ = precedentSynonyms.iterator();

      AbstractOptionSpec spec;
      do {
         if (!i$.hasNext()) {
            Set<OptionSpec<?>> associated = (Set)this.requiredIf.get(precedentSynonyms);
            if (associated == null) {
               associated = new HashSet();
               this.requiredIf.put(precedentSynonyms, associated);
            }

            ((Set)associated).add(required);
            return;
         }

         String each = (String)i$.next();
         spec = this.specFor(each);
      } while(spec != null);

      throw new UnconfiguredOptionException(precedentSynonyms);
   }

   private AbstractOptionSpec<?> specFor(char option) {
      return this.specFor(String.valueOf(option));
   }

   private AbstractOptionSpec<?> specFor(String option) {
      return (AbstractOptionSpec)this.recognizedOptions.get(option);
   }

   private void reset() {
      this.state = OptionParserState.moreOptions(this.posixlyCorrect);
   }

   private static char[] extractShortOptionsFrom(String argument) {
      char[] options = new char[argument.length() - 1];
      argument.getChars(1, argument.length(), options, 0);
      return options;
   }

   private void validateOptionCharacters(char[] options) {
      char[] arr$ = options;
      int len$ = options.length;

      for(int i$ = 0; i$ < len$; ++i$) {
         char each = arr$[i$];
         String option = String.valueOf(each);
         if (!this.isRecognized(option)) {
            throw OptionException.unrecognizedOption(option);
         }

         if (this.specFor(option).acceptsArguments()) {
            return;
         }
      }

   }

   private static KeyValuePair parseLongOptionWithArgument(String argument) {
      return KeyValuePair.valueOf(argument.substring(2));
   }

   private static KeyValuePair parseShortOptionWithArgument(String argument) {
      return KeyValuePair.valueOf(argument.substring(1));
   }

   private Map<String, List<?>> defaultValues() {
      Map<String, List<?>> defaults = new HashMap();
      Iterator i$ = this.recognizedOptions.toJavaUtilMap().entrySet().iterator();

      while(i$.hasNext()) {
         Entry<String, AbstractOptionSpec<?>> each = (Entry)i$.next();
         defaults.put(each.getKey(), ((AbstractOptionSpec)each.getValue()).defaultValues());
      }

      return defaults;
   }
}
