package com.matt.forgehax.util.command;

import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.matt.forgehax.util.SafeConverter;
import com.matt.forgehax.util.command.callbacks.OnChangeCallback;
import com.matt.forgehax.util.command.exception.CommandBuildException;
import com.matt.forgehax.util.console.ConsoleIO;
import com.matt.forgehax.util.serialization.ISerializableJson;
import com.matt.forgehax.util.typeconverter.TypeConverter;
import java.io.IOException;
import java.util.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/** Created on 6/2/2017 by fr1kin */
public class Setting<E> extends Command implements ISerializableJson {
  public static final String DEFAULTVALUE = "Setting.defaultValue";
  public static final String CONVERTER = "Setting.converter";
  public static final String COMPARATOR = "Setting.comparator";
  public static final String MINVALUE = "Setting.minvalue";
  public static final String MAXVALUE = "Setting.maxvalue";
  public static final String RESETAUTOGEN = "Setting.resetAutoGen";
  public static final String DEFAULTPROCESSOR = "Setting.defaultProcessor";

  private final E defaultValue;
  private final TypeConverter<E> converter;
  private final Comparator<E> comparator;

  private final E minValue;
  private final E maxValue;

  private E value;

  @SuppressWarnings("unchecked")
  protected Setting(Map<String, Object> data) throws CommandBuildException {
    super(data);
    try {
      this.converter = (TypeConverter<E>) data.get(CONVERTER);
      Objects.requireNonNull(this.converter, "Setting requires converter");

      this.defaultValue = (E) data.get(DEFAULTVALUE);
      this.comparator = (Comparator<E>) data.get(COMPARATOR);
      this.minValue = (E) data.get(MINVALUE);
      this.maxValue = (E) data.get(MAXVALUE);

      Boolean defaultProcessor = (Boolean) data.getOrDefault(DEFAULTPROCESSOR, true);
      if (defaultProcessor) {
        processors.add(
            in -> {
              in.requiredArguments(1);
              Object arg = in.getArgument(0);
              if (arg != null) {
                rawSet(String.valueOf(arg));
                serialize();
                in.markSuccess();
              } else in.markFailed();
            });
      }

      Boolean resetAutoGen = (Boolean) data.getOrDefault(RESETAUTOGEN, true);
      if (resetAutoGen) {
        parser.acceptsAll(Arrays.asList("r", "reset"), "Sets the command to its default value");
      }

      // set with constraints
      set(defaultValue, true);
    } catch (Throwable t) {
      throw new CommandBuildException("Failed to build setting", t);
    }
  }

  public E get() {
    return value;
  }

  public E getMin() {
    return minValue;
  }

  public E getMax() {
    return maxValue;
  }

  public E getDefault() {
    return defaultValue;
  }

  @Nonnull
  public Class<?> getType() {
    return converter.type();
  }

  public boolean getAsBoolean() {
    return SafeConverter.toBoolean(get());
  }

  public byte getAsByte() {
    return SafeConverter.toByte(get());
  }

  public char getAsCharacter() {
    return SafeConverter.toCharacter(get());
  }

  public double getAsDouble() {
    return SafeConverter.toDouble(get());
  }

  public float getAsFloat() {
    return SafeConverter.toFloat(get());
  }

  public int getAsInteger() {
    return SafeConverter.toInteger(get());
  }

  public long getAsLong() {
    return SafeConverter.toLong(get());
  }

  public short getAsShort() {
    return SafeConverter.toShort(get());
  }

  public String getAsString() {
    return converter.toString(get());
  }

  public boolean set(E value, boolean silent) {
    if (comparator != null && value != null && this.value != null) {
      // clamp value to minimum and maximum value
      if (minValue != null && comparator.compare(get(), minValue) < 0) value = minValue;
      else if (maxValue != null && comparator.compare(get(), maxValue) > 0) value = maxValue;
    }
    if (!Objects.equals(get(), value)) {
      if (!silent) {
        ConsoleIO.write(String.format("%s = %s", getAbsoluteName(), converter.toStringSafe(value)));
        OnChangeCallback<E> cb = new OnChangeCallback<>(this, get(), value);
        invokeCallbacks(CallbackType.CHANGE, cb);
        if (cb.isCanceled()) return false;
      }
      this.value = value;
      return true;
    }
    return false;
  }

  public boolean set(E value) {
    return set(value, false);
  }

  public boolean rawSet(String value, boolean silent) {
    return set(converter.parseSafe(value), silent);
  }

  public boolean rawSet(String value) {
    return rawSet(value, false);
  }

  public boolean reset() {
    return set(defaultValue);
  }

  public TypeConverter<E> getConverter() {
    return converter;
  }

  @Override
  public String getPrintText() {
    return getName() + " = " + getAsString() + " - " + getDescription();
  }

  @Override
  public boolean addChild(@Nonnull Command child) {
    throw new UnsupportedOperationException(
        "Command::addChild is not supported for a Setting type");
  }

  @Override
  public boolean removeChild(@Nonnull Command child) {
    return false;
  }

  @Nullable
  @Override
  public Command getChild(String name) {
    return null;
  }

  @Override
  public Collection<Command> getChildren() {
    return Collections.emptySet();
  }

  @Override
  public void getChildrenDeep(Collection<Command> all) {}

  @Override
  public Collection<Command> getChildrenDeep() {
    return Collections.emptySet();
  }

  @Override
  protected boolean preprocessor(String[] args) {
    if (args.length > 0) {
      String opt = args[0];
      if (opt.matches("-r|--reset")) {
        reset();
        serialize();
        return false;
      }
    }
    return true;
  }

  @Override
  public void serialize(JsonWriter writer) throws IOException {
    writer.beginObject();

    writer.name("value");
    writer.value(getAsString());

    writer.endObject();
  }

  @Override
  public void deserialize(JsonReader reader) throws IOException {
    reader.beginObject();

    reader.nextName(); // value
    rawSet(reader.nextString(), true);

    reader.endObject();
  }
}

@@ -516,17 +516,158 @@
 va.util.
-*
+Arrays;%0Aimport java.util.Collection;%0Aimport java.util.Collections;%0Aimport java.util.Comparator;%0Aimport java.util.Map;%0Aimport java.util.Objects
 ;%0Aimport
@@ -3985,21 +3985,21 @@
 compare(
-get()
+value
 , minVal
@@ -4079,21 +4079,21 @@
 compare(
-get()
+value
 , maxVal

Fix Setting clamp (min max values)