/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.jsr77.managedobject;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBDEPLOYMENT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.ObjectName;
import javax.management.QueryExp;

import org.jboss.as.jsr77.logging.JSR77Logger;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;


/**
 *
 * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
 */
public class J2EEDeployedObjectHandlers extends Handler {

    static final J2EEDeployedObjectHandlers INSTANCE = new J2EEDeployedObjectHandlers();

    static final String J2EE_TYPE_J2EE_APPLICATION = "J2EEApplication";
    static final String J2EE_TYPE_EJB_MODULE = "EjbModule";
    static final String J2EE_TYPE_WEB_MODULE = "WebModule";
    static final String J2EE_TYPE_RA_MODULE = "ResourceAdapterModule";
    static final String J2EE_TYPE_APP_CLIENT_MODULE = "AppClientModule";

    private J2EEDeployedObjectHandlers() {
    }

    @Override
    Set<ObjectName> queryObjectNames(final ModelReader reader, final ObjectName name, final QueryExp query) {
        //iterate deployment model to get all deployments
        final ModelNode deployments = reader.getDeploymentModel();
        if (!deployments.isDefined()) {
            return Collections.emptySet();
        }
        final Set<ObjectName> names = new HashSet<ObjectName>();
        for (ModelNode deployment : deployments.asList()) {
            //TODO This is weird - look into this again when my internet is working again
            //There seems to be an additional result + outcome level
            deployment = deployment.get(RESULT);

            final String deploymentName = deployment.get(NAME).asString();
            final String objName = createObjectName(
                    null,
                    deploymentName,
                    deployment.hasDefined(SUBSYSTEM) ? deployment.get(SUBSYSTEM).asString() : null);

            if (objName == null) {
                continue;
            }
            addMatchingObjectName(names, name, objName);

            if (deploymentName.endsWith(".ear")
                    && deployment.hasDefined(SUBDEPLOYMENT)) {
                for (Property prop : deployment.get(SUBDEPLOYMENT).asPropertyList()) {
                    final ModelNode subDep = prop.getValue();
                    final String subObjName = createObjectName(deploymentName, prop.getName(),
                            subDep.hasDefined(SUBSYSTEM) ? subDep.get(SUBSYSTEM).asString() : null);

                    addMatchingObjectName(names, name, subObjName);
                }
            }
        }

        return names;
    }

    private void addMatchingObjectName(final Set<ObjectName> names, final ObjectName name, final String objName) {
        if (objName != null) {
            ObjectName objectName = ObjectNameBuilder.createObjectName(objName);
            if (objectName != null && (name == null || name.apply(objectName))) {
                names.add(objectName);
            }
        }
    }

    private String createObjectName(final String appName, final String deploymentName, final String subsystem) {
        String type = null;
        if (deploymentName.endsWith(".ear")) {
            type = J2EE_TYPE_J2EE_APPLICATION;
        } else if (deploymentName.endsWith(".war")) {
            type = J2EE_TYPE_WEB_MODULE;
        } else if (deploymentName.endsWith(".jar")) {
            if (deploymentName.endsWith("client.jar")) {
                //TODO - HACK to pick out client jars for the tck
                type = J2EE_TYPE_APP_CLIENT_MODULE;
            } else if (subsystem != null && subsystem.equals("ejb3")){
                type = J2EE_TYPE_EJB_MODULE;
            }
        } else if (deploymentName.endsWith(".rar")) {
            type = J2EE_TYPE_RA_MODULE;
        }

        if (type == null) {
            return null;
        }

        final ObjectNameBuilder builder = ObjectNameBuilder.createServerChild(type, deploymentName);
        if (appName != null) {
            builder.append(J2EE_TYPE_J2EE_APPLICATION, appName);
        }
        return builder.toString();
    }

    @Override
    Object getAttribute(final ModelReader reader, final ObjectName name, final String attribute) throws AttributeNotFoundException, InstanceNotFoundException {
       return findHandler(reader, name).getAttribute(reader, name, attribute);
    }

    @Override
    MBeanInfo getMBeanInfo(ModelReader reader, ObjectName name) throws InstanceNotFoundException {
        return findHandler(reader, name).getMBeanInfo(reader, name);
    }

    Handler findHandler(final ModelReader reader, final ObjectName name) throws InstanceNotFoundException {
        //TODO parse object name and iterate deployment model to get deployment
        final String j2eeType = name.getKeyProperty(Handler.J2EE_TYPE);
        final String namePart = name.getKeyProperty(Handler.NAME);
        final String appName = name.getKeyProperty(J2EE_TYPE_J2EE_APPLICATION);

        final String mainDeployment;
        final String subDeployment;
        if (appName == null) {
            mainDeployment = namePart;
            subDeployment = null;
        } else {
            mainDeployment = appName;
            subDeployment = namePart;
        }

        if (mainDeployment != null) {
            //Look for the main deployment
            ModelNode deploymentNode = null;
            ModelNode deployments = reader.getDeploymentModel();
            if (deployments.isDefined()) {
                for (ModelNode deployment : deployments.asList()) {
                    deployment = deployment.get(RESULT);


                    if (mainDeployment.equals(deployment.get(NAME).asString())) {
                        String actualDeployment = mainDeployment;
                        deploymentNode = deployment;
                        if (!deploymentNode.isDefined()) {
                            break;
                        }
                        if (subDeployment != null) {
                            actualDeployment = subDeployment;
                            deploymentNode = deploymentNode.get(SUBDEPLOYMENT, subDeployment);
                            if (!deploymentNode.isDefined()) {
                                break;
                            }
                        }

                        J2EEDeployedObjectHandler handler;
                        if (j2eeType.equals(J2EE_TYPE_J2EE_APPLICATION) && actualDeployment.endsWith(".ear")){
                            handler = J2EEApplicationHandler.INSTANCE;
                        } else if (j2eeType.equals(J2EE_TYPE_APP_CLIENT_MODULE) && actualDeployment.endsWith(".jar")) {
                            handler = AppClientModuleHandler.INSTANCE;
                        } else if (j2eeType.equals(J2EE_TYPE_EJB_MODULE) && actualDeployment.endsWith(".jar") &&
                                (deploymentNode.hasDefined(SUBSYSTEM) && deploymentNode.get(SUBSYSTEM).asString().equals("ejb3"))) {
                            handler = EJBModuleHandler.INSTANCE;
                        } else if (j2eeType.equals(J2EE_TYPE_WEB_MODULE) && actualDeployment.endsWith(".war")) {
                            handler = WebModuleHandler.INSTANCE;
                        } else {
                            break;
                        }
                       reader.setDeploymentModel(deploymentNode);
                       return handler;
                    }
                }
            }
        }
        throw JSR77Logger.ROOT_LOGGER.couldNotFindJ2eeType(j2eeType);
    }

    private static class J2EEApplicationHandler extends J2EEDeployedObjectHandler {
        private static final J2EEApplicationHandler INSTANCE = new J2EEApplicationHandler();
        static final String ATTR_MODULES = "modules";

        @Override
        Set<ObjectName> queryObjectNames(ModelReader reader, ObjectName name, QueryExp query) {
            return Collections.singleton(name);
        }

        @Override
        Object getAttribute(ModelReader reader, ObjectName name, String attribute) throws AttributeNotFoundException {
            if (attribute.equals(ATTR_MODULES)) {
                //TODO implement if TCK does not like this
                return new String[0];
            }
            return super.getAttribute(reader, name, attribute);
        }

        @Override
        Set<MBeanAttributeInfo> getAttributeInfos() {
            Set<MBeanAttributeInfo> attributes = super.getAttributeInfos();

            attributes.add(createRoMBeanAttributeInfo(ATTR_MODULES, String[].class.getName(), "The modules in this application"));

            return attributes;
        }

    }

    private abstract static class J2EEModuleHandler extends J2EEDeployedObjectHandler {
        static final String ATTR_JAVA_VMS = "javaVMs";

        @Override
        Set<ObjectName> queryObjectNames(ModelReader reader, ObjectName name, QueryExp query) {
            return Collections.singleton(name);
        }

        @Override
        Object getAttribute(ModelReader reader, ObjectName name, String attribute) throws AttributeNotFoundException {
            if (attribute.equals(ATTR_JAVA_VMS)) {
                return new String[] {JVMHandler.INSTANCE.getObjectName()};
            }
            return super.getAttribute(reader, name, attribute);
        }

        @Override
        Set<MBeanAttributeInfo> getAttributeInfos() {
            Set<MBeanAttributeInfo> attributes = super.getAttributeInfos();

            attributes.add(createRoMBeanAttributeInfo(ATTR_JAVA_VMS, String[].class.getName(), "The jvms"));

            return attributes;
        }

    }

    private static class AppClientModuleHandler extends J2EEModuleHandler {
        static final AppClientModuleHandler INSTANCE = new AppClientModuleHandler();

        @Override
        Object getAttribute(ModelReader reader, ObjectName name, String attribute) throws AttributeNotFoundException {
            return super.getAttribute(reader, name, attribute);
        }
    }

    private static class WebModuleHandler extends J2EEModuleHandler {
        static final WebModuleHandler INSTANCE = new WebModuleHandler();

        static final String ATTR_SERVLETS = "servlets";

        @Override
        Object getAttribute(ModelReader reader, ObjectName name, String attribute) throws AttributeNotFoundException {
            if (attribute.equals(ATTR_SERVLETS)) {
                return new String[0];
            }
            return super.getAttribute(reader, name, attribute);
        }

        @Override
        Set<MBeanAttributeInfo> getAttributeInfos() {
            Set<MBeanAttributeInfo> attributes = super.getAttributeInfos();

            attributes.add(createRoMBeanAttributeInfo(ATTR_SERVLETS, String[].class.getName(), "The servlets"));

            return attributes;
        }
    }


    private static class EJBModuleHandler extends J2EEModuleHandler {
        static final EJBModuleHandler INSTANCE = new EJBModuleHandler();

        static final String ATTR_EJBS = "ejbs";

        @Override
        Object getAttribute(ModelReader reader, ObjectName name, String attribute) throws AttributeNotFoundException {
            if (attribute.equals(ATTR_EJBS)) {
                return new String[0];
            }
            return super.getAttribute(reader, name, attribute);
        }

        @Override
        Set<MBeanAttributeInfo> getAttributeInfos() {
            Set<MBeanAttributeInfo> attributes = super.getAttributeInfos();

            attributes.add(createRoMBeanAttributeInfo(ATTR_EJBS, String[].class.getName(), "The ejbs"));

            return attributes;
        }

    }
}
