package com.matt.forgehax.mods;

import static com.matt.forgehax.util.spam.SpamTokens.MESSAGE;
import static com.matt.forgehax.util.spam.SpamTokens.PLAYER_NAME;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.FutureCallback;
import com.matt.forgehax.Helper;
import com.matt.forgehax.events.ChatMessageEvent;
import com.matt.forgehax.events.PlayerConnectEvent;
import com.matt.forgehax.mods.services.SpamService;
import com.matt.forgehax.util.ArrayHelper;
import com.matt.forgehax.util.command.CommandHelper;
import com.matt.forgehax.util.command.Options;
import com.matt.forgehax.util.command.Setting;
import com.matt.forgehax.util.common.PriorityEnum;
import com.matt.forgehax.util.entity.PlayerInfo;
import com.matt.forgehax.util.entity.PlayerInfoHelper;
import com.matt.forgehax.util.entry.CustomMessageEntry;
import com.matt.forgehax.util.mod.Category;
import com.matt.forgehax.util.mod.ToggleMod;
import com.matt.forgehax.util.mod.loader.RegisterMod;
import com.matt.forgehax.util.spam.SpamMessage;
import com.matt.forgehax.util.spam.SpamTokens;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nullable;
import joptsimple.internal.Strings;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;

/**
 * Created on 7/21/2017 by fr1kin
 */
@RegisterMod
public class JoinMessage extends ToggleMod {
  
  private static final SpamTokens[] SPAM_TOKENS = new SpamTokens[]{PLAYER_NAME, MESSAGE};
  
  private final Options<CustomMessageEntry> messages =
      getCommandStub()
          .builders()
          .<CustomMessageEntry>newOptionsBuilder()
          .name("messages")
          .description("Custom messages")
          .factory(CustomMessageEntry::new)
          .supplier(Sets::newConcurrentHashSet)
          .build();
  
  private final Setting<String> keyword =
      getCommandStub()
          .builders()
          .<String>newSettingBuilder()
          .name("keyword")
          .description("Keyword for the join message")
          .defaultTo("!joinmessage")
          .build();
  
  private final Setting<String> format =
      getCommandStub()
          .builders()
          .<String>newSettingBuilder()
          .name("format")
          .description(
              "Join message format (Use {PLAYER_NAME} for the player joining, {MESSAGE} for the set message)")
          .defaultTo("<{PLAYER_NAME}> {MESSAGE}")
          .build();
  
  private final Setting<Long> delay =
      getCommandStub()
          .builders()
          .<Long>newSettingBuilder()
          .name("delay")
          .description("Delay between each message in ms")
          .defaultTo(15000L)
          .build();
  
  private final Setting<Integer> message_length =
      getCommandStub()
          .builders()
          .<Integer>newSettingBuilder()
          .name("message_length")
          .description("Maximum length of a custom message")
          .defaultTo(25)
          .build();
  
  private final Setting<Boolean> use_offline =
      getCommandStub()
          .builders()
          .<Boolean>newSettingBuilder()
          .name("use_offline")
          .description("Allows non-authenticated player names to be added")
          .defaultTo(false)
          .build();
  
  private final Setting<Long> set_cooldown =
      getCommandStub()
          .builders()
          .<Long>newSettingBuilder()
          .name("set_cooldown")
          .description("Setting cooldown for individual players in ms")
          .defaultTo(15000L)
          .build();
  
  private final Setting<Integer> max_player_messages =
      getCommandStub()
          .builders()
          .<Integer>newSettingBuilder()
          .name("max_player_messages")
          .description("Maximum number of messages per individual player")
          .defaultTo(5)
          .min(1)
          .max(Integer.MAX_VALUE)
          .changed(
              cb -> {
                messages.forEach(e -> e.setSize(cb.getTo()));
                messages.serialize();
              })
          .build();
  
  private final Setting<Boolean> debug_messages =
      getCommandStub()
          .builders()
          .<Boolean>newSettingBuilder()
          .name("debug_messages")
          .description("Displays messages in chat if a player fails to use the command properly")
          .defaultTo(false)
          .build();
  
  private final Map<UUID, AtomicLong> cooldowns = Maps.newConcurrentMap();
  
  public JoinMessage() {
    super(Category.MISC, "JoinMessage", false, "Allows players to add custom join messages");
  }
  
  private void debugMessage(String str) {
    if (debug_messages.get()) {
      Helper.printMessageNaked(
          Strings.EMPTY, str, new Style().setItalic(true).setColor(TextFormatting.GRAY));
    }
  }
  
  private void setJoinMessage(UUID target, UUID setter, String message) {
    CustomMessageEntry entry = messages.get(target);
    if (entry == null) {
      entry = new CustomMessageEntry(target);
      messages.add(entry);
    }
    
    String replyMessage = "Join message changed.";
    
    if (!entry.containsEntry(setter)) {
      entry.setSize(max_player_messages.get() - 1); // evict a random message
      replyMessage = "Join message set.";
    }
    entry.addMessage(setter, message); // correct size now
    
    // set cooldown
    cooldowns
        .computeIfAbsent(setter, s -> new AtomicLong(0L))
        .set(System.currentTimeMillis() + set_cooldown.get());
    
    messages.serialize();
    
    SpamService.send(
        new SpamMessage(replyMessage, "JOIN_MESSAGE_REPLY", 0, null, PriorityEnum.HIGHEST));
  }
  
  @SubscribeEvent
  public void onPlayerChat(ChatMessageEvent event) {
    String[] args = event.getMessage().split(" ");
    
    if (args.length < 3) {
      return; // not enough arguments
    }
    
    final String keyword = ArrayHelper.getOrDefault(args, 0, Strings.EMPTY);
    if (!this.keyword.get().equalsIgnoreCase(keyword)) {
      return;
    }
    
    final String target = ArrayHelper.getOrDefault(args, 1, Strings.EMPTY);
    if (target.length() > PlayerInfoHelper.MAX_NAME_LENGTH) {
      debugMessage("Input name over valid length");
      return;
    }
    if (target.equalsIgnoreCase(event.getSender().getName())) {
      debugMessage("Cannot set own join message");
      return;
    }
    
    final String message = CommandHelper.join(args, " ", 2, args.length);
    if (Strings.isNullOrEmpty(message)) {
      debugMessage("Invalid message (null or empty)");
      return;
    }
    if (message.length() > message_length.get()) {
      debugMessage("Message over maximum specified by JoinMessage.message_length");
      return;
    }
    
    // setter is not in cooldown
    if (System.currentTimeMillis()
        < cooldowns.getOrDefault(event.getSender().getId(), new AtomicLong(0L)).get()) {
      debugMessage("Player is currently in a cooldown");
      return;
    }
    
    if (use_offline.get()) {
      // use offline ID
      setJoinMessage(EntityPlayerSP.getOfflineUUID(target), event.getSender().getId(), message);
      return; // join message set, stop here
    }
    
    PlayerInfoHelper.registerWithCallback(
        target,
        new FutureCallback<PlayerInfo>() {
          @Override
          public void onSuccess(@Nullable PlayerInfo result) {
            if (result != null && !result.isOfflinePlayer()) {
              setJoinMessage(result.getId(), event.getSender().getId(), message);
            }
          }
          
          @Override
          public void onFailure(Throwable t) {
          }
        });
  }
  
  @SubscribeEvent
  public void onPlayerConnect(PlayerConnectEvent.Join event) {
    CustomMessageEntry entry = messages.get(event.getPlayerInfo().getId());
    if (entry != null) {
      // resize if needed
      if (entry.getSize() > max_player_messages.get()) {
        entry.setSize(max_player_messages.get());
      }
      SpamService.send(
          new SpamMessage(
              SpamTokens.fillAll(
                  format.get(),
                  SPAM_TOKENS,
                  event.getPlayerInfo().getName(),
                  entry.getRandomMessage()),
              "JOIN_MESSAGE",
              delay.get(),
              null,
              PriorityEnum.HIGH));
    }
  }
}