package hal9k;

import hal9k.Command;

import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;

import org.pircbotx.Colors;
import org.pircbotx.PircBotX;
import org.pircbotx.User;
import org.pircbotx.hooks.events.MessageEvent;
import org.pircbotx.hooks.types.GenericMessageEvent;

public class Commands
{
  HAL9K bot;
  Vote vote = null;
  HashMap<String, Method> commandMap;
  StreamManager streamManager;
  Timer tmr;

  Commands(HAL9K b)
  {
    this.bot = b;
    this.commandMap = getCommandMap();
    this.streamManager = new StreamManager(this.bot, 4);
  }

  private HashMap<String, Method> getCommandMap()
  {
    HashMap<String, Method> map = new HashMap<String, Method>();
    for (Method m : super.getClass().getMethods()) {
      if (m.isAnnotationPresent(Command.class))
        map.put(m.getAnnotation(Command.class).commandName().toLowerCase(), m);
    }
    return map;
  }

  public Method getCommand(String s) {
    return (this.commandMap.get(s.toLowerCase()));
  }

  public ArrayList<Method> getCommands() {
    ArrayList<Method> ret = new ArrayList<Method>();
    for (Method m : this.commandMap.values()) {
      ret.add(m);
    }
    return ret;
  }

  @Command(commandName="help", commandPrivileges=' ', commandHelp="Seriously?")
  public void help(GenericMessageEvent<HAL9K> event)
  {
    ArrayList<String> args = getArguments(event, " ");
    try {
      String comm = args.get(0).toLowerCase();
      if (comm.startsWith("!")) {
        comm = comm.substring(1);
      }
      if (this.commandMap.containsKey(comm)) {
        Method c = this.commandMap.get(comm);
        String helpStr = c.getAnnotation(Command.class).commandHelp();
        event.getUser().send().notice(helpStr);
        return;
      }
      event.getUser().send().notice("That is not a valid command.");
    }
    catch (IndexOutOfBoundsException e)
    {
      String available = "";
      for (Method m : getCommands()) {
        if (this.bot.hasPrivileges(event.getUser(), m, bot.getChannel())) {
          available = available + m.getAnnotation(Command.class).commandName() + ", ";
        }
      }
      event.getUser().send().notice("Use \"!help <Command>\". Available commands: " + available.substring(0, available.length() - 2));
    }
  }

  @Command(commandName="about", commandPrivileges=' ', commandHelp="Use \"!about\" to get info about this bot")
  public void about(GenericMessageEvent<HAL9K> event)
  {
    event.getUser().send().notice("My name is HAL9000. I am here to keep order in the chatroom and also ensure that everyone has a good time. To learn about my functions, type '!help'.");
  }

  @Command(commandName="whoIs", commandPrivileges='+', commandHelp="Use \"!whoIs <nick>\" to get more info about a user.")
  public void whoIs(GenericMessageEvent<HAL9K> event)
  {
    try
    {
      ArrayList<String> args = getArguments(event, " ");
      String nick = args.get(0);
      for (User u : bot.getChannel().getUsers()) {
        if (u.getNick().equalsIgnoreCase(nick)) {
        	event.getUser().send().notice("Nick: " + u.getNick());
        	event.getUser().send().notice("Hostmask: " + u.getHostmask());
        	event.getUser().send().notice("Login: " + u.getLogin());
          return;
        }
      }
      event.getUser().send().notice("User not found.");
    }
    catch (IndexOutOfBoundsException e) {
    	event.getUser().send().notice("Enter a user's nick to get info on that user.");
    }
  }

  @Command(commandName="sing", commandPrivileges=' ', commandHelp="Use \"!sing\" to let me sing you the song of my people.")
  public void sing(GenericMessageEvent<HAL9K> event)
  {
	  event.getUser().send().notice("Daisy. Daisy");
	  event.getUser().send().notice("Give me your answer, do");
	  event.getUser().send().notice("I'm half... Crazy");
	  event.getUser().send().notice("All for the love of you...");
  }

  @Command(commandName="echo", commandPrivileges='@', commandHelp="Use \"!echo <Message>\" to have HAL send a message to the channel.")
  public void echo(GenericMessageEvent<HAL9K> event)
  {
    try
    {
    	String message = event.getMessage();
    	if(message.length()>6){
    		message = event.getMessage().substring(event.getMessage().indexOf(" ") + 1);
    		this.bot.getChannel().send().message(message);}
    	else
    		throw new IndexOutOfBoundsException();
    }
    catch (IndexOutOfBoundsException e) {
    	event.getUser().send().notice("Please enter a message to have relayed to the chat");
    }
  }

  @Command(commandName="vote", commandPrivileges=' ', commandHelp="Use \"!vote <Option #>\" to participate in a vote. Ex. !vote 3")
  public void vote(GenericMessageEvent<HAL9K> event)
  {
    if (!(event instanceof MessageEvent)) {
      event.getUser().send().notice("Votes must be cast through a public message");
      return;
    }
    if(event.getMessage().equals("!vote standings"))
    	event.getUser().send().notice("Please use the command !voteStandings");
    else{
    
    	
    ArrayList<String> args = getArguments(event, " ");
    String response = "";
    try {
      int choice = Integer.parseInt(args.get(0)) - 1;
      response = this.vote.castVote(event.getUser(), choice);
    }
    catch (IndexOutOfBoundsException e) {
      response = "That is not a vaild vote option.";
    }
    catch (NumberFormatException e) {
      response = "Invalid. Use \"!vote <NUMBER>\" ex. !vote 3";
    }
    catch (NullPointerException e) {
      response = "There is no vote currently taking place.";
    }
    finally {
      event.getUser().send().notice(response);
    }
    }
  }

  @Command(commandName="newVote", commandPrivileges='@', commandHelp="Use \"!newVote <Option 1>, <Option 2>, ... [-time <mm:ss>]\" to make a new vote with the given options.")
  public void newVote(GenericMessageEvent<HAL9K> event)
  {
    if (this.vote != null) {
    	event.getUser().send().notice("There is already a vote taking place.");
    }
    else
    	try
      	{
    		ArrayList<String> args = getArguments(event, ",");
    		vote = new Vote(args);
    		/*for (Iterator<String> localIterator = getArguments(event).iterator(); localIterator.hasNext(); message = message + sEvent + " ") sEvent = localIterator.next();
        	if (message.equals("")) throw new IllegalArgumentException("You did not enter any options.");*/
        
    		/*String[] titles = args.toArray(new String[args.size()]);
    		ArrayList<String> options = new ArrayList<String>();
    		for (String s1 : titles) {
    			if (!(s1.trim().equals("")))
    				options.add(s1.trim());
    		}*/
    		if(vote.timed){
    			tmr = new Timer();
    			tmr.schedule(new TimerTask(){
	        		@Override
	        		public void run(){
	        			if(vote!=null){
	        				bot.noticeChannel("The vote has ended.");
	        				bot.noticeChannel(String.format("The final results are: %s", vote.getStandings()));
	        				vote=null;
	        			}
	        		}
	        	}, vote.time*1000L);
        		
    			this.bot.noticeChannel(String.format("A new vote has started. It will automatically end in %s %s.", vote.minutes+"m", vote.seconds+"s"));
    		}
    		else
    			this.bot.noticeChannel("A new vote has started.");
        
    		this.bot.noticeChannel(this.vote.getOptions());
      	}
      	catch (IllegalArgumentException e) {
      		event.getUser().send().notice(e.getMessage());
      	}
  }

  @Command(commandName="endVote", commandPrivileges='@', commandHelp="Use \"!endVote\" to end the current vote and display the results to the entire channel.")
  public void endVote(GenericMessageEvent<HAL9K> event)
  {
    if (this.vote == null) {
      event.getUser().send().notice("There is no vote currently taking place.");
    }
    else {
      this.bot.noticeChannel("The vote has ended.");
      this.bot.noticeChannel("The final results are: " + this.vote.getStandings());
      vote = null;
    }
  }

  @Command(commandName="voteOptions", commandPrivileges=' ', commandHelp="Use \"!voteOptions\" to view the available options for the current vote.")
  public void voteOptions(GenericMessageEvent<HAL9K> event)
  {
    try
    {
      event.getUser().send().notice(this.vote.getOptions());
    } catch (NullPointerException e) {
      event.getUser().send().notice("There is no vote currently taking place.");
    }
  }

  @Command(commandName="voteOptionsAll", commandPrivileges='@', commandHelp="Use \"!voteOptionsAll\" to show the available options of the current vote to everybody.")
  public void voteOptionsAll(GenericMessageEvent<HAL9K> event)
  {
    try
    {
      this.bot.noticeChannel(this.vote.getOptions());
    } catch (NullPointerException e) {
      event.getUser().send().notice("There is no vote currently taking place.");
    }
  }

  @Command(commandName="voteStandings", commandPrivileges=' ', commandHelp="Use \"!voteStandings\" to see the current standings of the ongoing vote.")
  public void voteStandings(GenericMessageEvent<HAL9K> event)
  {
    try
    {
      event.getUser().send().notice("The current vote standings are: " + this.vote.getStandings());
    } catch (NullPointerException e) {
      event.getUser().send().notice("There is no vote currently taking place.");
    }
  }

  @Command(commandName="streaming", commandPrivileges=' ', commandHelp="Use \"!streaming\" to see which movies are currently playing.")
  public void streaming(GenericMessageEvent<HAL9K> event)
  {
    this.streamManager.updateStreams();
    bot.sendNotice(event.getUser(), (this.streamManager.showStreamingMovies()));
  }

  @Command(commandName="playlist", commandPrivileges=' ', commandHelp="Use \"!playlist <Stream #>\" to view the playlist of a stream. Ex. !playlist 2")
  public void playlist(GenericMessageEvent<HAL9K> event)
  {
    try
    {
      bot.sendNotice(event.getUser(), (this.streamManager.showPlaylist(getArguments(event, " "))));
    }
    catch (IllegalArgumentException e) {
      event.getUser().send().notice(e.getMessage());
    }
  }

  @Command(commandName="addMovie", commandPrivileges='+', commandHelp="Use \"!addMovie <Stream #> <Movie Title> [-time <Length>]\" to append a movie to a stream's playlist. Use a -time tag at the end to specify the length of the movie. Example: \"!addMovie 1 Kicking and Screaming -time 1:25\"")
  public void addMovie(GenericMessageEvent<HAL9K> event)
  {
    try
    {
      this.bot.noticeChannel(this.streamManager.addMovie(event.getUser(), getArguments(event, " ")));
    }
    catch (IllegalArgumentException e) {
      event.getUser().send().notice(e.getMessage());
    }
  }

  @Command(commandName="insertMovie", commandPrivileges='+', commandHelp="Use \"!insertMovie <Stream #> <Playlist Index #> <Movie Title> [-time <Length>] to place a movie into a specific position of a stream's playlist. Example: \"!insertMovie 1 2 The Squid and the Whale -time 124\"")
  public void insertMovie(GenericMessageEvent<HAL9K> event)
  {
    try
    {
      this.bot.noticeChannel(this.streamManager.insertMovie(event.getUser(), getArguments(event, " ")));
    }
    catch (IllegalArgumentException e) {
      event.getUser().send().notice(e.getMessage());
    }
  }

  @Command(commandName="replaceMovie", commandPrivileges='+', commandHelp="Use \"!replaceMovie <Stream #> <Playlist Index #> <Movie Title> [-time <Length>] to replace and remove an existing movie in a stream's playlist.")
  public void replaceMovie(GenericMessageEvent<HAL9K> event)
  {
    try
    {
      this.bot.noticeChannel(this.streamManager.replaceMovie(event.getUser(), getArguments(event, " ")));
    }
    catch (IllegalArgumentException e) {
      event.getUser().send().notice(e.getMessage());
    }
  }

  @Command(commandName="clear", commandPrivileges='+', commandHelp="Use \"!clear <Stream #> [Playlist Index #]\" to clear an item from a stream. If no playlist index is given, the currently playing movie on that stream will be cleared.")
  public void clear(GenericMessageEvent<HAL9K> event)
  {
    try
    {
      this.bot.noticeChannel(this.streamManager.clear(event.getUser(), getArguments(event, " ")));
    }
    catch (IllegalArgumentException e) {
      event.getUser().send().notice(e.getMessage());
    }
  }

  @Command(commandName="clearPlaylist", commandPrivileges='+', commandHelp="Use \"!clearPlaylist <Stream #>\" to clear the entire playlist of a stream")
  public void clearPlaylist(GenericMessageEvent<HAL9K> event)
  {
    try
    {
      this.bot.noticeChannel(this.streamManager.clearPlaylist(event.getUser(), getArguments(event, " ")));
    }
    catch (IllegalArgumentException e) {
      event.getUser().send().notice(e.getMessage());
    }
  }

  @Command(commandName="setLength", commandPrivileges='+', commandHelp="Use \"!setLength <Stream #> <Playlist Index #> <Time>\" to set the length of a movie in a stream's playlist.")
  public void setLength(GenericMessageEvent<HAL9K> event)
  {
    try
    {
      this.bot.noticeChannel(this.streamManager.setLength(event.getUser(), getArguments(event, " ")));
    }
    catch (IllegalArgumentException e) {
      event.getUser().send().notice(e.getMessage());
    }
  }

  @Command(commandName="setTime", commandPrivileges='+', commandHelp="Use \"!setTime <Stream #> <Time>\" to set the current time of the playing movie on a stream.")
  public void setTime(GenericMessageEvent<HAL9K> event)
  {
    try
    {
      this.bot.noticeChannel(this.streamManager.setTime(event.getUser(), getArguments(event, " ")));
    }
    catch (IllegalArgumentException e) {
      event.getUser().send().notice(e.getMessage());
    }
  }
  
  @Command(commandName="savePlaylists", commandPrivileges='%', commandHelp="Use \"!savePlaylist\" to save the current playlist to a file(Playlist.txt).")
  public void savePlaylists(GenericMessageEvent<HAL9K> event)
  {
	try {
		streamManager.savePlaylistToFile();
		if(event != null)
			this.bot.noticeChannel(String.format("User %s has saved the current playlists.", event.getUser().getNick()));
	} catch (FileNotFoundException e) {
		// TODO Auto-generated catch block
		bot.logger.warning(e.getMessage());
	} catch (UnsupportedEncodingException e) {
		// TODO Auto-generated catch block
		bot.logger.warning(e.getMessage());
	}
	  
  }
  
  public void updateStreams()
  {
    this.streamManager.updateStreams();
  }

  private ArrayList<String> getArguments(GenericMessageEvent<HAL9K> event, String regex)
  {
    ArrayList<String> args = new ArrayList<String>();
    ArrayList<String> targs = new ArrayList<String>();
    if(regex==" "){
    	for (String s : Colors.removeFormattingAndColors(event.getMessage()).split(" ")){
        	args.add(s);      	
        }
    	args.remove(0);
    }
    else{
    	for (String s : Colors.removeFormattingAndColors(event.getMessage()).split(regex)) {
    		targs.add(s);
    	}
    	if(targs.get(targs.size()-1).contains("-time")){
    		String tA = targs.remove(targs.size()-1);
    		String[] tB = tA.split("-time");
    		targs.add(tB[0]);
    		targs.add("-time");
    		targs.add(tB[1]);
    	}
    	String[] t1 = targs.remove(0).split(" ");
    	if(t1.length>2){
    		for(int i = 2; i<t1.length; i++)
    			t1[1] = t1[1].concat(" "+t1[i]);}
    	args.add(t1[1]);
    	args.addAll(targs);
    }
    
    for(int i=0; i<args.size(); i++)
    	args.set(i, args.get(i).trim());
    while (args.remove(""));
    this.bot.logger.info("Arguments: " + args);
    return args;
  }
}