/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011-2013 Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the @author tags. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * 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,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */
package org.infinispan.server.endpoint.subsystem;

import static org.infinispan.server.endpoint.subsystem.PrefixResource.PREFIX_ATTRIBUTES;
import static org.infinispan.server.endpoint.subsystem.SniResource.SNI_ATTRIBUTES;

import java.util.Collections;
import java.util.List;

import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;

import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleListAttributeDefinition;
import org.jboss.as.controller.StringListAttributeDefinition;
import org.jboss.as.controller.persistence.SubsystemMarshallingContext;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import org.jboss.dmr.Property;
import org.jboss.staxmapper.XMLElementWriter;
import org.jboss.staxmapper.XMLExtendedStreamWriter;

/**
 * The XML writer for the endpoint subsystem configuration.
 *
 * @author <a href="http://gleamynode.net/">Trustin Lee</a>
 * @author <a href="http://www.dataforte.net/blog/">Tristan Tarrant</a>
 *
 */
class EndpointSubsystemWriter implements XMLStreamConstants, XMLElementWriter<SubsystemMarshallingContext> {

   EndpointSubsystemWriter() {
   }

   @Override
   public void writeContent(final XMLExtendedStreamWriter writer, final SubsystemMarshallingContext context)
         throws XMLStreamException {
      context.startSubsystemElement(Namespace.CURRENT.getUri(), false);
      final ModelNode node = context.getModelNode();
      writeConnectors(writer, node);
      writer.writeEndElement();
   }

   private void writeConnectors(final XMLExtendedStreamWriter writer, final ModelNode node) throws XMLStreamException {
      for (Property property : getConnectorsByType(node, ModelKeys.HOTROD_CONNECTOR)) {
         writeHotRodConnector(writer, property.getValue());
      }
      for (Property property : getConnectorsByType(node, ModelKeys.MEMCACHED_CONNECTOR)) {
         writeMemcachedConnector(writer, property.getValue());
      }
      for (Property property : getConnectorsByType(node, ModelKeys.REST_CONNECTOR)) {
         writeRestConnector(writer, property.getValue());
      }
      for (Property property : getConnectorsByType(node, ModelKeys.WEBSOCKET_CONNECTOR)) {
         writeWebSocketConnector(writer, property.getValue());
      }
      for (Property property : getConnectorsByType(node, ModelKeys.ROUTER_CONNECTOR)) {
         writeRouterConnector(writer, property.getValue());
      }
   }

   private List<Property> getConnectorsByType(final ModelNode node, String connectorType) {
      if (node.hasDefined(connectorType)) {
         ModelNode connectors = node.get(connectorType);
         return connectors.asPropertyList();
      } else {
         return Collections.emptyList();
      }
   }

   private void writeHotRodConnector(final XMLExtendedStreamWriter writer, final ModelNode connector)
         throws XMLStreamException {
      writer.writeStartElement(Element.HOTROD_CONNECTOR.getLocalName());
      writeCommonConnector(writer, connector);
      writeProtocolServerConnector(writer, connector);
      writeTopologyStateTransfer(writer, connector);
      writeAuthentication(writer, connector);
      writeEncryption(writer, connector);
      writer.writeEndElement();
   }

   private void writeMemcachedConnector(final XMLExtendedStreamWriter writer, final ModelNode connector)
         throws XMLStreamException {
      writer.writeStartElement(Element.MEMCACHED_CONNECTOR.getLocalName());
      writeCommonConnector(writer, connector);
      writeProtocolServerConnector(writer, connector);
      for (SimpleAttributeDefinition attribute : MemcachedConnectorResource.MEMCACHED_CONNECTOR_ATTRIBUTES) {
         attribute.marshallAsAttribute(connector, true, writer);
      }
      writer.writeEndElement();
   }

   private void writeRestConnector(final XMLExtendedStreamWriter writer, final ModelNode connector)
         throws XMLStreamException {
      writer.writeStartElement(Element.REST_CONNECTOR.getLocalName());
      writeCommonConnector(writer, connector);
      for (SimpleAttributeDefinition attribute : RestConnectorResource.REST_ATTRIBUTES) {
         attribute.marshallAsAttribute(connector, true, writer);
      }
      writeRestAuthentication(writer, connector);
      writeEncryption(writer, connector);
      writer.writeEndElement();
   }

   private void writeWebSocketConnector(final XMLExtendedStreamWriter writer, final ModelNode connector)
         throws XMLStreamException {
      writer.writeStartElement(Element.WEBSOCKET_CONNECTOR.getLocalName());
      writeCommonConnector(writer, connector);
      writeProtocolServerConnector(writer, connector);
      writer.writeEndElement();
   }

   private void writeRouterConnector(final XMLExtendedStreamWriter writer, final ModelNode connector)
           throws XMLStreamException {
      writer.writeStartElement(Element.ROUTER_CONNECTOR.getLocalName());
      for (SimpleAttributeDefinition attribute : RouterConnectorResource.ROUTER_CONNECTOR_ATTRIBUTES) {
         attribute.marshallAsAttribute(connector, true, writer);
      }
      writer.writeStartElement(Element.MULTI_TENANCY.getLocalName());
      ModelNode multiTenancy = connector.get(ModelKeys.MULTI_TENANCY, ModelKeys.MULTI_TENANCY_NAME);
      writeHotrod(writer, multiTenancy);
      writeRest(writer, multiTenancy);
      writer.writeEndElement(); //multi-tenancy
      writer.writeEndElement(); //router-connector
   }

   private void writeHotrod(XMLExtendedStreamWriter writer, ModelNode parentNode) throws XMLStreamException {
      if (parentNode.hasDefined(ModelKeys.HOTROD)) {
         for (ModelNode hotrodNode: parentNode.get(ModelKeys.HOTROD).asList()) {
            writer.writeStartElement(Element.HOTROD.getLocalName());
            for (SimpleAttributeDefinition hotrodAttribute : RouterHotRodResource.ROUTER_HOTROD_ATTRIBUTES) {
               hotrodAttribute.marshallAsAttribute(hotrodNode.get(0), true, writer);
            }
            writeSni(writer, hotrodNode.get(0));
            writer.writeEndElement();
         }
      }
   }

   private void writeRest(XMLExtendedStreamWriter writer, ModelNode parentNode) throws XMLStreamException {
      if (parentNode.hasDefined(ModelKeys.REST)) {
         for (ModelNode hotrodNode: parentNode.get(ModelKeys.REST).asList()) {
            writer.writeStartElement(Element.REST.getLocalName());
            for (SimpleAttributeDefinition hotrodAttribute : RouterRestResource.ROUTER_REST_ATTRIBUTES) {
               hotrodAttribute.marshallAsAttribute(hotrodNode.get(0), true, writer);
            }
            writePrefix(writer, hotrodNode.get(0));
            writer.writeEndElement();
         }
      }
   }

   private void writeCommonConnector(final XMLExtendedStreamWriter writer, final ModelNode connector)
         throws XMLStreamException {
      for (SimpleAttributeDefinition attribute : CommonConnectorResource.COMMON_CONNECTOR_ATTRIBUTES) {
         attribute.marshallAsAttribute(connector, true, writer);
      }
      SimpleListAttributeDefinition ignoredCaches = CommonConnectorResource.IGNORED_CACHES;
      writeListAsAttribute(writer, ignoredCaches.getXmlName(), connector, ignoredCaches.getName());
   }

   private void writeProtocolServerConnector(final XMLExtendedStreamWriter writer, final ModelNode connector)
         throws XMLStreamException {
      for (SimpleAttributeDefinition attribute : ProtocolServerConnectorResource.PROTOCOL_SERVICE_ATTRIBUTES) {
         attribute.marshallAsAttribute(connector, true, writer);
      }
   }

   private void writeTopologyStateTransfer(final XMLExtendedStreamWriter writer, final ModelNode connector)
         throws XMLStreamException {
      if (connector.hasDefined(ModelKeys.TOPOLOGY_STATE_TRANSFER)) {
         ModelNode topologyStateTransfer = connector.get(ModelKeys.TOPOLOGY_STATE_TRANSFER,
               ModelKeys.TOPOLOGY_STATE_TRANSFER_NAME);
         writer.writeStartElement(Element.TOPOLOGY_STATE_TRANSFER.getLocalName());
         for (SimpleAttributeDefinition attribute : TopologyStateTransferResource.TOPOLOGY_ATTRIBUTES) {
            attribute.marshallAsAttribute(topologyStateTransfer, true, writer);
         }
         writer.writeEndElement();
      }
   }

   private void writeSni(final XMLExtendedStreamWriter writer, final ModelNode parentNode) throws XMLStreamException {
      if (parentNode.hasDefined(ModelKeys.SNI)) {
         for (ModelNode mapping: parentNode.get(ModelKeys.SNI).asList()) {
            writer.writeStartElement(Element.SNI.getLocalName());
            for (SimpleAttributeDefinition sniMappingAttribute : SNI_ATTRIBUTES) {
               sniMappingAttribute.marshallAsAttribute(mapping.get(0), true, writer);
            }
            writer.writeEndElement();
         }
      }
   }

   private void writePrefix(final XMLExtendedStreamWriter writer, final ModelNode parentNode) throws XMLStreamException {
      if (parentNode.hasDefined(ModelKeys.PREFIX)) {
         for (ModelNode mapping: parentNode.get(ModelKeys.PREFIX).asList()) {
            writer.writeStartElement(Element.PREFIX.getLocalName());
            for (SimpleAttributeDefinition prefixMappingAttribute : PREFIX_ATTRIBUTES) {
               prefixMappingAttribute.marshallAsAttribute(mapping.get(0), true, writer);
            }
            writer.writeEndElement();
         }
      }
   }

   private void writeAuthentication(final XMLExtendedStreamWriter writer, final ModelNode connector)
           throws XMLStreamException {
      if (connector.hasDefined(ModelKeys.AUTHENTICATION)) {
         ModelNode authentication = connector.get(ModelKeys.AUTHENTICATION, ModelKeys.AUTHENTICATION_NAME);
         writer.writeStartElement(Element.AUTHENTICATION.getLocalName());
         for (SimpleAttributeDefinition attribute : AuthenticationResource.AUTHENTICATION_ATTRIBUTES) {
            attribute.marshallAsAttribute(authentication, true, writer);
         }
         writeSasl(writer, authentication);
         writer.writeEndElement();
      }
   }

   private void writeSasl(final XMLExtendedStreamWriter writer, final ModelNode authentication) throws XMLStreamException {
      if (authentication.hasDefined(ModelKeys.SASL)) {
         ModelNode sasl = authentication.get(ModelKeys.SASL, ModelKeys.SASL_NAME);
         writer.writeStartElement(Element.SASL.getLocalName());
         for (AttributeDefinition attribute : SaslResource.SASL_ATTRIBUTES) {
            if (attribute instanceof SimpleAttributeDefinition) {
               ((SimpleAttributeDefinition)attribute).marshallAsAttribute(sasl, true, writer);
            } else if (attribute instanceof StringListAttributeDefinition) {
               writeListAsAttribute(writer, attribute.getXmlName(), sasl, attribute.getName());
            }
         }
         writePolicy(writer, sasl);
         writeProperties(writer, sasl);
         writer.writeEndElement();
      }
   }

   private void writePolicy(final XMLExtendedStreamWriter writer, final ModelNode sasl) throws XMLStreamException {
      if (sasl.hasDefined(ModelKeys.SASL_POLICY)) {
         ModelNode policy = sasl.get(ModelKeys.SASL_POLICY, ModelKeys.SASL_POLICY_NAME);
         writer.writeStartElement(Element.POLICY.getLocalName());
         SaslPolicyResource.FORWARD_SECRECY.marshallAsElement(policy, writer);
         SaslPolicyResource.NO_ACTIVE.marshallAsElement(policy, writer);
         SaslPolicyResource.NO_ANONYMOUS.marshallAsElement(policy, writer);
         SaslPolicyResource.NO_DICTIONARY.marshallAsElement(policy, writer);
         SaslPolicyResource.NO_PLAIN_TEXT.marshallAsElement(policy, writer);
         SaslPolicyResource.PASS_CREDENTIALS.marshallAsElement(policy, writer);
         writer.writeEndElement();
      }
   }

   private void writeRestAuthentication(final XMLExtendedStreamWriter writer, final ModelNode connector)
         throws XMLStreamException {
      if (connector.hasDefined(ModelKeys.AUTHENTICATION)) {
         ModelNode authentication = connector.get(ModelKeys.AUTHENTICATION, ModelKeys.AUTHENTICATION_NAME);
         writer.writeStartElement(Element.AUTHENTICATION.getLocalName());
         for (SimpleAttributeDefinition attribute : RestAuthenticationResource.AUTHENTICATION_ATTRIBUTES) {
            attribute.marshallAsAttribute(authentication, true, writer);
         }
         writer.writeEndElement();
      }
   }

   private void writeProperties(XMLExtendedStreamWriter writer, ModelNode sasl) throws XMLStreamException {
      if (sasl.hasDefined(ModelKeys.PROPERTY)) {
         for (Property property: sasl.get(ModelKeys.PROPERTY).asPropertyList()) {
            writer.writeStartElement(Element.PROPERTY.getLocalName());
            writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
            Property complexValue = property.getValue().asProperty();
            writer.writeCharacters(complexValue.getValue().asString());
            writer.writeEndElement();
         }
      }
   }

   private void writeEncryption(final XMLExtendedStreamWriter writer, final ModelNode connector)
         throws XMLStreamException {
      if (connector.hasDefined(ModelKeys.ENCRYPTION)) {
         ModelNode encryption = connector.get(ModelKeys.ENCRYPTION, ModelKeys.ENCRYPTION_NAME);
         writer.writeStartElement(Element.ENCRYPTION.getLocalName());
         for (SimpleAttributeDefinition attribute : EncryptionResource.ENCRYPTION_ATTRIBUTES) {
            attribute.marshallAsAttribute(encryption, true, writer);
         }
         writeSni(writer, encryption);
         writer.writeEndElement();
      }
   }

   private void writeListAsAttribute(XMLExtendedStreamWriter writer, String attributeName, ModelNode node, String key) throws XMLStreamException {
      if (node.hasDefined(key)) {
         StringBuilder result = new StringBuilder();
         ModelNode list = node.get(key);
         if (list.isDefined() && list.getType() == ModelType.LIST) {
            List<ModelNode> nodeList = list.asList();
            for (int i = 0; i < nodeList.size(); i++) {
               result.append(nodeList.get(i).asString());
               if (i < nodeList.size() - 1) {
                  result.append(" ");
               }
            }
            writer.writeAttribute(attributeName, result.toString());
         }
      }
   }
}
