package jetbrains.buildServer.serverSide;

import com.intellij.openapi.diagnostic.Logger;
import jetbrains.buildServer.users.User;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author Eugene Petrenko
 *         Created: 31.03.2010 12:42:50
 */
public class ChatModel {
  private final static Logger LOG = Logger.getInstance(ChatModel.class.getName());

  private final TimeFactory myTime;
  private final InitialMessages myInitialMessages;
  private final Rooms myRooms;
  private final UserContextFactory myContext;
  private final Randomization myRandomization;

  private final Map<Integer, List<ChatMessage>> myMessages = new ConcurrentHashMap<Integer, List<ChatMessage>>();

  public ChatModel(InitialMessages initialMessages, TimeFactory time, Rooms rooms, UserContextFactory context, Randomization randomization) {
    myInitialMessages = initialMessages;
    myTime = time;
    myRooms = rooms;
    myContext = context;
    myRandomization = randomization;
  }

  private List<ChatMessage> getInitialMessages() {
    return myInitialMessages.getInitialMessages();
  }

  private List<ChatMessage> getUserMessages(int roomId) {
    final List<ChatMessage> chatMessages = myMessages.get(roomId);
    if(chatMessages == null) {
      CopyOnWriteArrayList<ChatMessage> list = new CopyOnWriteArrayList<ChatMessage>(getInitialMessages());
      myMessages.put(roomId, list);
      return list;
    }
    return chatMessages;
  }

  public ChatMessagesCollection initializeChat(@Nullable User user) {
    //Ensure map contains a root
    final int id = myRooms.getRoomIdByUser(user);
    getUserMessages(id);
    
    return new ChatMessagesCollection(0, Collections.<ChatMessage>emptyList(), myContext.getUserNamesForUserSession(user), myTime, Collections.singleton(id));
  }

  public ChatMessagesCollection getMessagesToUser(@Nullable User user, int offset) {
    final int roomId = myRooms.getRoomIdByUser(user);
    final List<ChatMessage> pMessages = getUserMessages(roomId);
    return prepareMessages(user, offset, pMessages, Collections.singleton(roomId));
  }

  public ChatMessagesCollection getAllMessages(int offset) {
    List<ChatMessage> allMessages = new ArrayList<ChatMessage>();
    for (List<ChatMessage> list : myMessages.values()) {
      allMessages.addAll(list);
    }

    Collections.sort(allMessages, new Comparator<ChatMessage>() {
      public int compare(ChatMessage o1, ChatMessage o2) {
        return o1.getDate().compareTo(o2.getDate());
      }
    });

    return prepareMessages(null, offset, allMessages, myRooms.getAllRooms());
  }

  private ChatMessagesCollection prepareMessages(@Nullable User user, int offset, List<ChatMessage> pMessages, Collection<Integer> rooms) {
    int i = 0;
    final List<ChatMessage> result = new ArrayList<ChatMessage>();
    for (ChatMessage msg : pMessages) {
      if (i >= offset) {
        result.add(msg);
      }
      i++;
    }
    final int nextId = result.isEmpty() ? pMessages.size() : (offset + result.size());
    return new ChatMessagesCollection(nextId, result, myContext.getUserNamesForUserSession(user), myTime, rooms);
  }

  @NotNull
  private ChatMessage parseToUser(ChatMessage msg) {
    String message = msg.getMessage().trim();
    if (!message.startsWith("@")) return msg;

    int space = message.indexOf(' ');
    if (space <= 0) return msg;

    final User toUser = myContext.findToUser(message.substring(1, space));
    if (toUser == null) return msg;
    return msg.setToUser(toUser);
  }

  private ChatMessage parseBroadcast(ChatMessage msg) {
    String message = msg.getMessage().trim();
    if (!message.startsWith("@all")) return msg;

    message = message.substring("@all".length());

    return msg.setBroadcast(true).setMessage(message);
  }

  @Nullable
  private ChatMessage parseFromUser(ChatMessage msg) {
    String message = msg.getMessage().trim();

    if (!message.startsWith("$")) return msg;

    int space = message.indexOf(' ');
    if (space <= 0) return msg;

    final User fromUser = myContext.findToUser(message.substring(1, space));
    if (fromUser == null) return msg;

    return msg.setFromUser(fromUser).setMessage(message.substring(space+1, message.length()));
  }

  private ChatMessage createMessage(User fromUser, String message) {
    final ChatMessage msg = new ChatMessage(myTime.getTime(), message, fromUser, null);
    return parseBroadcast(parseToUser(parseFromUser(msg)));
  }

  @NotNull
  public ChatMessagesCollection postMessage(@Nullable User fromUser, String message, int offset) {
    final ChatMessage msg = createMessage(fromUser, message);
    User user = msg.getFromUser();

    final Set<Integer> usersRoomId = selectRoomsToPost(msg);
    myRooms.pingUser(user);
    
    for (Integer id : usersRoomId) {
      LOG.warn(msg.toString());
      getUserMessages(id).add(msg);
    }

    if (user != null) {
      scheduleEcho(message, usersRoomId);
    }

    return getMessagesToUser(fromUser, offset);
  }

  private Set<Integer> selectRoomsToPost(ChatMessage msg) {
    User user = msg.getFromUser();

    if (user == null || msg.isBroadcast()) {
      return myRooms.getAllRooms();
    }

    final User toUser = msg.getToUser();

    Set<Integer> set = new HashSet<Integer>();
    set.add(myRooms.getRoomIdByUser(user));
    
    if (toUser != null) {
      set.add(myRooms.getRoomIdByUser(toUser));
    }


    return set;
  }

  private User selectUserForEchoPost(int roomId) {
    final List<ChatMessage> mgs = getUserMessages(roomId);

    if (mgs.size() <= 1) return null;

    return myRandomization.selectOneOf(mgs, .7).getFromUser();
  }

  private void scheduleEcho(String message, Set<Integer> roomsToSkip) {
    final Collection<Integer> rooms = myRandomization.selectSomeOf(myRooms.getRoomsForEcho(roomsToSkip), 0.05);

    for (Integer roomId : rooms) {
      getUserMessages(roomId).add(createMessage(selectUserForEchoPost(roomId), "[[echo]]: " + message));
    }
  }
}
