/**
 * Copyright (C) 2011 eZuce Inc. All rights reserved.
 *
 * 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 org.jivesoftware.spark.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;

import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.jivesoftware.gui.LoginUIPanel;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.spark.ButtonFactory;
import org.jivesoftware.spark.component.tabbedPane.SparkTabbedPane;
import org.jivesoftware.spark.ui.ChatContainer;
import org.jivesoftware.spark.ui.ChatRoom;
import org.jivesoftware.spark.ui.CommandPanel;
import org.jivesoftware.spark.ui.ContactGroup;
import org.jivesoftware.spark.ui.ContactInfoWindow;
import org.jivesoftware.spark.ui.ContactItem;
import org.jivesoftware.spark.ui.ContactList;
import org.jivesoftware.spark.ui.transctipt.TranscriptWindow;
import org.jivesoftware.spark.ui.conferences.ConferenceServices;
import org.jivesoftware.spark.ui.conferences.GroupChatParticipantList;
import org.jivesoftware.spark.ui.rooms.ChatRoomImpl;
import org.jivesoftware.spark.ui.rooms.GroupChatRoom;
import org.jivesoftware.spark.ui.status.StatusBar;
import org.jivesoftware.spark.ui.themes.ThemePanel;
import org.jivesoftware.spark.util.log.Log;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.EntityJid;
import org.jxmpp.jid.parts.Resourcepart;

import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.jivesoftware.spark.ChatManager.TESTING_JID;

/**
 * This is a registry for components that may be replaced by plugins.
 * Also doubles as a factory to instantiate those components.
 */
public final class UIComponentRegistry {
    // use Spark defaults, so without any plugins we still have Spark's functionality
    private static Class<? extends ContactItem> contactItemClass = ContactItem.class;
    private static Class<? extends ContactInfoWindow> contactInfoWindowClass = ContactInfoWindow.class;
    private static Class<? extends ContactGroup> contactGroupClass = ContactGroup.class;
    private static Class<? extends ContactList> contactListClass = ContactList.class;
    private static Class<? extends StatusBar> statusBarClass = StatusBar.class;
    private static Class<? extends CommandPanel> commandPanelClass = CommandPanel.class;
    private static Class<? extends SparkTabbedPane> workspaceTabPaneClass = SparkTabbedPane.class;
    private static Class<? extends LoginUIPanel> loginDialogClass = LoginUIPanel.class;
    private static Class<? extends ThemePanel> themePanelClass = ThemePanel.class;
    private static Class<? extends ConferenceServices> conferenceServicesClass = ConferenceServices.class;
    private static Class<? extends TranscriptWindow> transcriptWindowClass = TranscriptWindow.class;
    private static Class<? extends ChatRoom> chatRoomClass = ChatRoomImpl.class;
    private static Class<? extends GroupChatRoom> groupChatRoomClass = GroupChatRoom.class;
    private static Class<? extends GroupChatParticipantList> groupChatParticipantListClass = GroupChatParticipantList.class;
    private static Class<? extends ChatContainer> chatContainerClass = ChatContainer.class;
    private static Class<? extends ButtonFactory> buttonFactoryClass = ButtonFactory.class;

    private UIComponentRegistry() {
    }

    /**
     * Registers a new class implementing a contact item.
     */
    public static void registerLoginDialog(Class<? extends LoginUIPanel> clazz) {
        if (loginDialogClass != clazz) {
            Log.debug("Registering new contract item class: " + clazz.getName());
            loginDialogClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    /**
     * Registers a new class implementing a contact item.
     */
    public static void registerContactItem(Class<? extends ContactItem> clazz) {
        if (contactItemClass != clazz) {
            Log.debug("Registering new contract item class: " + clazz.getName());
            contactItemClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    /**
     * Registers a new class implementing a contact info window.
     */
    public static void registerContactInfoWindow(
        Class<? extends ContactInfoWindow> clazz) {
        if (contactInfoWindowClass != clazz) {
            Log.debug("Registering new contact info window class: " + clazz.getName());
            contactInfoWindowClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    /**
     * Registers a new class implementing a contact group.
     */
    public static void registerContactGroup(Class<? extends ContactGroup> clazz) {
        if (contactGroupClass != clazz) {
            Log.debug("Registering new contact group class: " + clazz.getName());
            contactGroupClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    /**
     * Registers a new class implementing a contact group.
     */
    public static void registerStatusBar(Class<? extends StatusBar> clazz) {
        if (statusBarClass != clazz) {
            Log.debug("Registering new status bar class: " + clazz.getName());
            statusBarClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    /**
     * Registers a new class implementing a command panel.
     */
    public static void registerCommandPanel(Class<? extends CommandPanel> clazz) {
        if (commandPanelClass != clazz) {
            Log.debug("Registering new command panel class: " + clazz.getName());
            commandPanelClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    /**
     * Registers a new class implementing a tab panel, for use within the main
     * application window.
     */
    public static void registerWorkspaceTabPanel(Class<? extends SparkTabbedPane> clazz) {
        if (workspaceTabPaneClass != clazz) {
            Log.debug("Registering new search panel class: " + clazz.getName());
            workspaceTabPaneClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    /**
     * Registers a new class implementing a contact group. Registers a new class
     * implementing a contact list.
     */
    public static void registerContactList(Class<? extends ContactList> clazz) {
        if (contactListClass != clazz) {
            Log.debug("Registering new contact list class: " + clazz.getName());
            contactListClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    /**
     * Registers a new class implementing a theme panel.
     */
    public static void registerThemePanel(Class<? extends ThemePanel> clazz) {
        if (themePanelClass != clazz) {
            Log.debug("Registering new theme panel class: " + clazz.getName());
            themePanelClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    /**
     * Registers a new class implementing conference services.
     */
    public static void registerConferenceServices(Class<? extends ConferenceServices> clazz) {
        if (conferenceServicesClass != clazz) {
            Log.debug("Registering new conference services class: " + clazz.getName());
            conferenceServicesClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    /**
     * Registers a new class implementing transcript window.
     */
    public static void registerTranscriptWindow(Class<? extends TranscriptWindow> clazz) {
        if (transcriptWindowClass != clazz) {
            Log.debug("Registering new transcript window class: " + clazz.getName());
            transcriptWindowClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    /**
     * Registers a new class implementing a chat room.
     */
    public static void registerChatRoom(Class<? extends ChatRoom> clazz) {
        if (chatRoomClass != clazz) {
            Log.debug("Registering new chat room class: " + clazz.getName());
            chatRoomClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    public static void registerGroupChatRoom(Class<? extends GroupChatRoom> clazz) {
        if (groupChatRoomClass != clazz) {
            Log.debug("Registering new group chat room class: " + clazz.getName());
            groupChatRoomClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    public static void registerGroupChatParticipantList(Class<? extends GroupChatParticipantList> clazz) {
        if (groupChatParticipantListClass != clazz) {
            Log.debug("Registering new group chat participant list class: " + clazz.getName());
            groupChatParticipantListClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    /**
     * Registers a new class implementing a chat room.
     */
    public static void registerChatContainer(Class<? extends ChatContainer> clazz) {
        if (chatContainerClass != clazz) {
            Log.debug("Registering new chat room class: " + clazz.getName());
            chatContainerClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    /**
     * Registers a new class implementing a button factory.
     */
    public static void registerButtonFactory(Class<? extends ButtonFactory> clazz) {
        if (buttonFactoryClass != clazz) {
            Log.debug("Registering new button factory class: " + clazz.getName());
            buttonFactoryClass = clazz;
        } else {
            Log.warning("Class " + clazz.getName() + " already registered.");
        }
    }

    /**
     * Creates a new contact item object.
     *
     * @return A new instance of the class currently registered as contact item.
     */
    public static ContactItem createContactItem(String alias, String nickname, BareJid fullyQualifiedJID) {
        // null breaks instantiation by reflection
        nickname = trimToEmpty(nickname);
        alias = trimToEmpty(alias);
        requireNonNull(fullyQualifiedJID);
        return instantiate(contactItemClass, alias, nickname, fullyQualifiedJID);
    }

    /**
     * Creates a new contact group item object.
     */
    public static ContactItem createContactGroupItem(String alias) {
        return instantiate(contactItemClass, alias, "", TESTING_JID);
    }

    /**
     * Retrieves the contact info window instance (this is implemented as a singleton).
     *
     * @return The instance of the class currently registered as contact info window.
     */
    public static ContactInfoWindow getContactInfoWindow() {
        ContactInfoWindow instance = null;
        try {
            final Method m = contactInfoWindowClass.getMethod("getInstance");
            final Object o = m.invoke(contactInfoWindowClass);
            instance = contactInfoWindowClass.cast(o);
        } catch (final Exception e) {
            // not pretty but we're catching 5 exceptions we can do little about
            Log.error("Error calling getInstance for " + contactInfoWindowClass.getName(), e);
        }
        return instance;
    }

    /**
     * Creates a new contact group object.
     *
     * @return A new instance of the class currently registered as contact group.
     */
    public static ContactGroup createContactGroup(String name, boolean sharedGroup, boolean offlineGroup, boolean unfiledGroup) {
        return instantiate(contactGroupClass, name, sharedGroup, offlineGroup, unfiledGroup);
    }

    /**
     * Creates a new contact group object
     *
     * @return A new instance of the class currently registered as status bar.
     */
    public static StatusBar createStatusBar() {
        return instantiate(statusBarClass);
    }

    /**
     * Creates a new command panel object
     *
     * @return A new instance of the class currently registered as status bar.
     */
    public static CommandPanel createCommandPanel() {
        return instantiate(commandPanelClass);
    }

    /**
     * Creates a new workspace tab panel object
     *
     * @return A new instance of the class currently registered as workspace tab pane
     */
    public static SparkTabbedPane createWorkspaceTabPanel(int tabPosition) {
        return instantiate(workspaceTabPaneClass, tabPosition);
    }

    /**
     * Creates a new login dialog panel object
     *
     * @return A new instance of the class currently registered as login dialog
     */
    public static LoginUIPanel createLoginDialog() {
        return instantiate(loginDialogClass);
    }

    /**
     * Creates a new contact list object.
     *
     * @return A new instance of the class currently registered as contact list.
     */
    public static ContactList createContactList() {
        return instantiate(contactListClass);
    }

    /**
     * Creates a new theme panel object.
     *
     * @return A new instance of the class currently registered as theme panel.
     */
    public static ThemePanel createThemePanel() {
        return instantiate(themePanelClass);
    }

    /**
     * Creates a new conference services object.
     *
     * @return A new instance of the class currently registered as conference services.
     */
    public static ConferenceServices createConferenceServices() {
        return instantiate(conferenceServicesClass);
    }

    /**
     * Creates a new transcript window object.
     *
     * @return A new instance of the class currently registered as transcript window.
     */
    public static TranscriptWindow createTranscriptWindow() {
        return instantiate(transcriptWindowClass);
    }

    /**
     * Creates a new chat room object.
     *
     * @return A new instance of the class currently registered as chat room.
     */
    public static ChatRoom createChatRoom(EntityJid participantJID, Resourcepart participantNickname, CharSequence title) {
        return instantiate(chatRoomClass, participantJID, participantNickname, title);
    }

    public static GroupChatRoom createGroupChatRoom(MultiUserChat muc) {
        return instantiate(groupChatRoomClass, muc);
    }

    public static GroupChatParticipantList createGroupChatParticipantList() {
        return instantiate(groupChatParticipantListClass);
    }

    /**
     * Creates a new chat container object.
     *
     * @return A new instance of the class currently registered as chat container.
     */
    public static ChatContainer createChatContainer() {
        return instantiate(chatContainerClass);
    }

    /**
     * Retrieves the button factory instance (this is implemented as a singleton).
     *
     * @return The instance of the class currently registered as button factory.
     */
    public static ButtonFactory getButtonFactory() {
        ButtonFactory instance = null;
        try {
            final Method m = buttonFactoryClass.getMethod("getInstance");
            final Object o = m.invoke(buttonFactoryClass);
            instance = buttonFactoryClass.cast(o);
        } catch (final Exception e) {
            // not pretty but we're catching 5 exceptions we can do little about
            Log.error("Error calling getInstance for " + buttonFactoryClass.getName(), e);
        }

        return instance;
    }

    /**
     * Instantiate a given class.
     *
     * @param currentClass Class to instantiate.
     * @param args         Arguments for the class constructor.
     */
    private static <T> T instantiate(Class<? extends T> currentClass, Object... args) {
        T instance = null;
        Class<?>[] classes = new Class<?>[args.length];
        try {
            for (int i = 0; i < args.length; i++) {
                classes[i] = args[i].getClass();
            }
            final Constructor<? extends T> ctor = ConstructorUtils.getMatchingAccessibleConstructor(currentClass, classes);
            instance = ctor.newInstance(args);
            Log.debug("Instantiated " + currentClass.getName() + " " + Arrays.toString(args));
        } catch (final Exception e) {
            // not pretty but we're catching several exceptions we can do little about
            Log.error("Error calling constructor for " + currentClass.getName() + " " + Arrays.toString(classes), e);
        }
        return instance;
    }

}
