/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.java.sip.communicator.impl.protocol.irc;

import java.util.*;

import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.muc.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;

/**
 * Allows creating, configuring, joining and administering of individual
 * text-based conference rooms.
 *
 * @author Stephane Remy
 * @author Loic Kempf
 * @author Yana Stamcheva
 * @author Lubomir Marinov
 * @author Danny van Heumen
 */
public class OperationSetMultiUserChatIrcImpl
    extends AbstractOperationSetMultiUserChat
{
    /**
     * A call back to the IRC provider that created us.
     */
    private ProtocolProviderServiceIrcImpl ircProvider = null;

    /**
     * A list of the rooms that are currently open by this account. Note that
     * we have not necessarily joined these rooms, we might have simply been
     * searching through them.
     */
    private final Map<String, ChatRoomIrcImpl> chatRoomCache
        = new Hashtable<String, ChatRoomIrcImpl>();

    /**
     * The <tt>ChatRoom</tt> corresponding to the IRC server channel. This chat
     * room is not returned by any of methods getExistingChatRooms(),
     * getCurrentlyJoinedChatRooms, etc.
     */
    private ChatRoomIrcImpl serverChatRoom;

    /**
     * Instantiates the user operation set with a currently valid instance of
     * the irc protocol provider.
     * @param provider a currently valid instance of
     * ProtocolProviderServiceIrcImpl.
     */
    public OperationSetMultiUserChatIrcImpl(
        final ProtocolProviderServiceIrcImpl provider)
    {
        this.ircProvider = provider;
    }

    /**
     * Returns the <tt>List</tt> of <tt>ChatRoom</tt>s currently available on
     * the server that this protocol provider is connected to.
     *
     * @return a <tt>java.util.List</tt> of <tt>ChatRoom</tt>s that are
     * currently available on the server that this protocol provider is
     * connected to.
     *
     * @throws OperationFailedException if we failed retrieving this list from
     * the server.
     */
    public List<String> getExistingChatRooms() throws OperationFailedException
    {
        final IrcConnection connection =
            this.ircProvider.getIrcStack().getConnection();
        if (connection == null)
        {
            throw new IllegalStateException("Connection is not available.");
        }
        return connection.getServerChannelLister().getList();
    }

    /**
     * Returns a list of the chat rooms that we have joined and are currently
     * active in.
     *
     * @return a <tt>List</tt> of the rooms where the user has joined using a
     * given connection.
     */
    public List<ChatRoom> getCurrentlyJoinedChatRooms()
    {
        synchronized (chatRoomCache)
        {
            List<ChatRoom> joinedRooms
                = new LinkedList<ChatRoom>(this.chatRoomCache.values());

            Iterator<ChatRoom> joinedRoomsIter = joinedRooms.iterator();

            while (joinedRoomsIter.hasNext())
            {
                if (!joinedRoomsIter.next().isJoined())
                {
                    joinedRoomsIter.remove();
                }
            }

            return joinedRooms;
        }
    }

    /**
     * Returns a list of the chat rooms that <tt>chatRoomMember</tt> has joined
     * and is currently active in.
     *
     * @param chatRoomMember the chat room member whose current ChatRooms we
     * will be querying.
     * @return a list of the chat rooms that <tt>chatRoomMember</tt> has joined
     * and is currently active in.
     */
    public List<String> getCurrentlyJoinedChatRooms(
        final ChatRoomMember chatRoomMember)
    {
        // Implement "who is" for the IRC stack.
        // (currently not in use)
        /*
         * According to the RFC:
         *
         * 311 RPL_WHOISUSER "<nick> <user> <host> * :<real name>"
         *
         * 312 RPL_WHOISSERVER "<nick> <server> :<server info>"
         *
         * 313 RPL_WHOISOPERATOR "<nick> :is an IRC operator"
         *
         * 317 RPL_WHOISIDLE "<nick> <integer> :seconds idle"
         *
         * 318 RPL_ENDOFWHOIS "<nick> :End of /WHOIS list"
         *
         * 319 RPL_WHOISCHANNELS "<nick> :{[@|+]<channel><space>}"
         *
         * - Replies 311 - 313, 317 - 319 are all replies generated in response
         * to a WHOIS message. Given that there are enough parameters present,
         * the answering server must either formulate a reply out of the above
         * numerics (if the query nick is found) or return an error reply. The
         * '*' in RPL_WHOISUSER is there as the literal character and not as a
         * wild card. For each reply set, only RPL_WHOISCHANNELS may appear more
         * than once (for long lists of channel names). The '@' and '+'
         * characters next to the channel name indicate whether a client is a
         * channel operator or has been granted permission to speak on a
         * moderated channel. The RPL_ENDOFWHOIS reply is used to mark the end
         * of processing a WHOIS message.
         */
        return Collections.emptyList();
    }

    /**
     * Creates a room with the named <tt>roomName</tt> and according to the
     * specified <tt>roomProperties</tt> on the server that this protocol
     * provider is currently connected to. When the method returns the room the
     * local user will not have joined it and thus will not receive messages on
     * it until the <tt>ChatRoom.join()</tt> method is called.
     * <p>
     * @param roomName the name of the <tt>ChatRoom</tt> to create.
     * @param roomProperties properties specifying how the room should be
     * created.
     * @throws OperationFailedException if the room couldn't be created for some
     * reason (e.g. room already exists; user already joined to an existent
     * room or user has no permissions to create a chat room).
     * @throws OperationNotSupportedException if chat room creation is not
     * supported by this server
     *
     * @return the newly created <tt>ChatRoom</tt> named <tt>roomName</tt>.
     */
    public ChatRoom createChatRoom(
            final String roomName,
            final Map<String, Object> roomProperties)
        throws OperationFailedException,
               OperationNotSupportedException
    {
        try
        {
            return findOrCreateRoom(roomName);
        }
        catch (IllegalArgumentException e)
        {
            String message =
                IrcActivator.getResources().getI18NString(
                    "service.gui.CREATE_CHAT_ROOM_ERROR", new String[]
                    {roomName});
            throw new OperationFailedException(message,
                OperationFailedException.ILLEGAL_ARGUMENT, e);
        }
    }

    /**
     * Returns a reference to a chatRoom named <tt>roomName</tt>.
     *
     * Originally, this method would create the room if it doesn't exist. This
     * is not acceptable anymore, since rebuilding the chat room list would
     * create new instances without the IRC stack being prepared for this or
     * having corresponding instances.
     *
     * @param roomName the name of the <tt>ChatRoom</tt> that we're looking for.
     * @return the <tt>ChatRoom</tt> named <tt>roomName</tt>.
     */
    public ChatRoomIrcImpl findRoom(final String roomName)
    {
        return chatRoomCache.get(roomName);
    }

    /**
     * Find an existing room with the provided name, or create a new room with
     * this name.
     *
     * @param roomName name of the chat room
     * @return returns a chat room
     */
    public ChatRoomIrcImpl findOrCreateRoom(final String roomName)
    {
        synchronized (this.chatRoomCache)
        {
            ChatRoomIrcImpl room = chatRoomCache.get(roomName);
            if (room == null)
            {
                room = createLocalChatRoomInstance(roomName);
            }
            return room;
        }
    }

    /**
     * There is no such thing as a rejection to an invitatation. The notion of
     * an invite in IRC is just an addition to a white list. There is nothing to
     * reject.
     *
     * @param invitation the invitation we are rejecting.
     * @param reason the reason of rejecting
     */
    public void rejectInvitation(final ChatRoomInvitation invitation,
        final String reason)
    {
    }

    /**
     * Returns true if <tt>contact</tt> supports multi-user chat sessions.
     *
     * @param contact reference to the contact whose support for chat rooms
     * we are currently querying.
     * @return a boolean indicating whether <tt>contact</tt> supports chat
     * rooms.
     */
    public boolean isMultiChatSupportedByContact(final Contact contact)
    {
        return true;
    }

    /**
     * Returns a reference to the chat room named <tt>chatRoomName</tt> or
     * null if the room hasn't been cached yet.
     *
     * @param chatRoomName the name of the room we're looking for.
     *
     * @return the <tt>ChatRoomJabberImpl</tt> instance that has been cached
     * for <tt>chatRoomName</tt> or null if no such room has been cached so far.
     */
    protected ChatRoomIrcImpl getChatRoom(final String chatRoomName)
    {
        return this.chatRoomCache.get(chatRoomName);
    }

    /**
     * Creates a <tt>ChatRoom</tt> from the specified chat room name.
     *
     * Must be used in SYNCHRONIZED context.
     *
     * @param chatRoomName the name of the chat room to add
     *
     * @return ChatRoom the chat room that we've just created.
     */
    private ChatRoomIrcImpl createLocalChatRoomInstance(
        final String chatRoomName)
    {
        ChatRoomIrcImpl chatRoom =
            new ChatRoomIrcImpl(chatRoomName, ircProvider);

        this.chatRoomCache.put(chatRoom.getName(), chatRoom);

        return chatRoom;
    }

    /**
     * Register chat room instance in case it is not yet registered.
     *
     * @param chatroom the chatroom
     */
    public void registerChatRoomInstance(final ChatRoomIrcImpl chatroom)
    {
        synchronized (this.chatRoomCache)
        {
            this.chatRoomCache.put(chatroom.getIdentifier(), chatroom);
        }
    }

    /**
     * Delivers a <tt>ChatRoomInvitationReceivedEvent</tt> to all
     * registered <tt>ChatRoomInvitationListener</tt>s.
     *
     * @param targetChatRoom the room that invitation refers to
     * @param inviter the inviter that sent the invitation
     * @param reason the reason why the inviter sent the invitation
     * @param password the password to use when joining the room
     */
    protected void fireInvitationEvent(final ChatRoom targetChatRoom,
                                    final String inviter,
                                    final String reason,
                                    final byte[] password)
    {
        ChatRoomInvitationIrcImpl invitation
            = new ChatRoomInvitationIrcImpl(targetChatRoom,
                                            inviter,
                                            reason,
                                            password);

        fireInvitationReceived(invitation);
    }

    /**
     * Returns the room corresponding to the server channel.
     *
     * @return the room corresponding to the server channel
     */
    protected ChatRoomIrcImpl findSystemRoom()
    {
        if (serverChatRoom == null)
        {
            serverChatRoom = new ChatRoomIrcImpl(
                ircProvider.getAccountID().getService(),
                ircProvider,
                true); // is system room

            this.fireLocalUserPresenceEvent(
                serverChatRoom,
                LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_JOINED,
                "Connected to the server.");
        }

        return serverChatRoom;
    }

    /**
     * Returns the system room member.
     *
     * @return the system room member.
     */
    protected ChatRoomMemberIrcImpl findSystemMember()
    {
        if (serverChatRoom.getMembers().size() > 0)
        {
            return (ChatRoomMemberIrcImpl) serverChatRoom.getMembers().get(0);
        }
        else
        {
            return new ChatRoomMemberIrcImpl(ircProvider, serverChatRoom,
                ircProvider.getAccountID().getService(), "", ircProvider
                    .getAccountID().getServerAddress(),
                ChatRoomMemberRole.GUEST, IrcStatusEnum.ONLINE);
        }
    }

    /**
     * {@inheritDoc}
     *
     * Always returns <tt>true</tt>.
     */
    @Override
    public boolean isPrivateMessagingContact(final String contactAddress)
    {
        return true;
    }

    /**
     * Open a chat room window.
     *
     * In IRC a situation may occur where the user gets joined to a channel
     * without Jitsi initiating the joining activity. This "unannounced" join
     * event, must also be handled and we should display the chat room window in
     * that case, to alert the user that this happened.
     *
     * @param chatRoom the chat room
     */
    void openChatRoomWindow(final ChatRoomIrcImpl chatRoom)
    {
        MUCService mucService = IrcActivator.getMUCService();
        UIService uiService = IrcActivator.getUIService();
        ChatRoomWrapper wrapper =
            mucService.getChatRoomWrapperByChatRoom(chatRoom, true);
        uiService.openChatRoomWindow(wrapper);
    }
}
