/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You 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.apache.geode.admin.internal;

import java.io.InputStream;
import java.util.Stack;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

import org.apache.geode.admin.AdminXmlException;
import org.apache.geode.admin.CacheServerConfig;
import org.apache.geode.admin.DistributedSystemConfig;
import org.apache.geode.admin.DistributionLocatorConfig;
import org.apache.geode.admin.ManagedEntityConfig;
import org.apache.geode.internal.Assert;

/**
 * Parses an XML file and configures a {@link DistributedSystemConfig} from it.
 *
 * @since GemFire 4.0
 */
@Deprecated
public class ManagedEntityConfigXmlParser extends ManagedEntityConfigXml implements ContentHandler {

  /** The <code>DistributedSystemConfig</code> to be configured */
  private DistributedSystemConfig config;

  /** The stack of intermediate values used while parsing */
  private final Stack stack = new Stack();

  ////////////////////// Static Methods //////////////////////

  /**
   * Parses XML data and from it configures a <code>DistributedSystemConfig</code>.
   *
   * @throws AdminXmlException If an error is encountered while parsing the XML
   */
  public static void parse(InputStream is, DistributedSystemConfig config) {
    ManagedEntityConfigXmlParser handler = new ManagedEntityConfigXmlParser();
    handler.config = config;

    try {
      SAXParserFactory factory = SAXParserFactory.newInstance();
      factory.setValidating(true);
      SAXParser parser = factory.newSAXParser();
      parser.parse(is, new DefaultHandlerDelegate(handler));

    } catch (Exception ex) {
      if (ex instanceof AdminXmlException) {
        throw (AdminXmlException) ex;

      } else if (ex.getCause() instanceof AdminXmlException) {
        throw (AdminXmlException) ex.getCause();

      } else if (ex instanceof SAXException) {
        // Silly JDK 1.4.2 XML parser wraps RunTime exceptions in a
        // SAXException. Pshaw!

        SAXException sax = (SAXException) ex;
        Exception cause = sax.getException();
        if (cause instanceof AdminXmlException) {
          throw (AdminXmlException) cause;
        }
      }

      throw new AdminXmlException(
          "While parsing XML", ex);
    }
  }

  /**
   * Helper method for parsing an integer
   *
   * @throws org.apache.geode.cache.CacheXmlException If <code>s</code> is a malformed integer
   */
  private static int parseInt(String s) {
    try {
      return Integer.parseInt(s);

    } catch (NumberFormatException ex) {
      throw new AdminXmlException(
          String.format("Malformed integer %s", s),
          ex);
    }
  }

  ////////////////////// Instance Methods //////////////////////

  @Override
  public void startElement(String namespaceURI, String localName, String qName, Attributes atts)
      throws SAXException {

    if (qName.equals(DISTRIBUTED_SYSTEM)) {
      startDistributedSystem(atts);

    } else if (qName.equals(REMOTE_COMMAND)) {
      startRemoteCommand(atts);

    } else if (qName.equals(LOCATORS)) {
      startLocators(atts);

    } else if (qName.equals(MULTICAST)) {
      startMulticast(atts);

    } else if (qName.equals(LOCATOR)) {
      startLocator(atts);

    } else if (qName.equals(HOST)) {
      startHost(atts);

    } else if (qName.equals(WORKING_DIRECTORY)) {
      startWorkingDirectory(atts);

    } else if (qName.equals(PRODUCT_DIRECTORY)) {
      startProductDirectory(atts);

    } else if (qName.equals(SSL)) {
      startSSL(atts);

    } else if (qName.equals(PROTOCOLS)) {
      startProtocols(atts);

    } else if (qName.equals(CIPHERS)) {
      startCiphers(atts);

    } else if (qName.equals(PROPERTY)) {
      startProperty(atts);

    } else if (qName.equals(KEY)) {
      startKey(atts);

    } else if (qName.equals(VALUE)) {
      startValue(atts);

    } else if (qName.equals(CACHE_SERVER)) {
      startCacheServer(atts);

    } else if (qName.equals(CLASSPATH)) {
      startClassPath(atts);

    } else {
      throw new AdminXmlException(
          String.format("Unknown XML element %s",
              qName));
    }
  }

  @Override
  public void endElement(String namespaceURI, String localName, String qName) throws SAXException {

    if (qName.equals(DISTRIBUTED_SYSTEM)) {
      endDistributedSystem();

    } else if (qName.equals(REMOTE_COMMAND)) {
      endRemoteCommand();

    } else if (qName.equals(LOCATORS)) {
      endLocators();

    } else if (qName.equals(MULTICAST)) {
      endMulticast();

    } else if (qName.equals(LOCATOR)) {
      endLocator();

    } else if (qName.equals(HOST)) {
      endHost();

    } else if (qName.equals(WORKING_DIRECTORY)) {
      endWorkingDirectory();

    } else if (qName.equals(PRODUCT_DIRECTORY)) {
      endProductDirectory();

    } else if (qName.equals(SSL)) {
      endSSL();

    } else if (qName.equals(PROTOCOLS)) {
      endProtocols();

    } else if (qName.equals(CIPHERS)) {
      endCiphers();

    } else if (qName.equals(PROPERTY)) {
      endProperty();

    } else if (qName.equals(KEY)) {
      endKey();

    } else if (qName.equals(VALUE)) {
      endValue();

    } else if (qName.equals(CACHE_SERVER)) {
      endCacheServer();

    } else if (qName.equals(CLASSPATH)) {
      endClassPath();

    } else {
      throw new AdminXmlException(
          String.format("Unknown XML element %s",
              qName));
    }
  }

  /**
   * When a <code>distributed-system</code> element is encountered, we push the
   * <code>DistributedSystemConfig</code> on the stack.
   */
  private void startDistributedSystem(Attributes atts) {
    Assert.assertTrue(stack.isEmpty());

    String id = atts.getValue(ID);
    if (id != null) {
      config.setSystemId(id);
    }

    String disable_tcp = atts.getValue(DISABLE_TCP);
    if (disable_tcp != null) {
      config.setDisableTcp(DISABLE_TCP.equalsIgnoreCase("true"));
    }

    stack.push(config);
  }

  /**
   * When a <code>distributed-system</code> element is finished
   */
  private void endDistributedSystem() {

  }

  /**
   * When a <code>multicast</code> is first encountered, get the
   * <code>DistributedSystemConfig</code> off of the top of the stack and set its multicast config
   * appropriately.
   */
  private void startMulticast(Attributes atts) {
    DistributedSystemConfig config = (DistributedSystemConfig) stack.peek();

    String port = atts.getValue(PORT);
    config.setMcastPort(parseInt(port));

    String address = atts.getValue(ADDRESS);
    if (address != null) {
      config.setMcastAddress(address);
    }
  }

  private void endMulticast() {

  }

  /**
   * Starts a <code>remote-command</code> element. The item on top of the stack may be a
   * <code>DistributedSystemConfig</code> or it might be a <code>ManagedEntityConfig</code>.
   */
  private void startRemoteCommand(Attributes atts) {

  }

  /**
   * Ends a <code>remote-command</code> element. Pop the command off the top of the stack and set it
   * on the <code>DistributedSystemConfig</code> or it might be a <code>ManagedEntityConfig</code>
   * on top of the stack.
   */
  private void endRemoteCommand() {
    String remoteCommand = popString();
    Object top = stack.peek();
    Assert.assertTrue(top != null);

    if (top instanceof DistributedSystemConfig) {
      ((DistributedSystemConfig) top).setRemoteCommand(remoteCommand);

    } else if (top instanceof ManagedEntityConfig) {
      ((ManagedEntityConfig) top).setRemoteCommand(remoteCommand);

    } else {
      String s = "Did not expect a " + top.getClass().getName() + " on top of the stack";
      Assert.assertTrue(false, s);
    }
  }

  private void startLocators(Attributes atts) {

  }

  private void endLocators() {

  }

  private void startLocator(Attributes atts) {
    String port = atts.getValue(PORT);

    DistributedSystemConfig system = (DistributedSystemConfig) stack.peek();
    system.setMcastPort(0);

    DistributionLocatorConfig config = system.createDistributionLocatorConfig();

    config.setPort(parseInt(port));

    stack.push(config);
  }

  private void endLocator() {
    Object o = stack.pop();
    Assert.assertTrue(o instanceof DistributionLocatorConfig);
  }

  private void startHost(Attributes atts) {

  }

  /**
   * We assume that there is a <code>ManagedEntityConfig</code> on top of the stack.
   */
  private void endHost() {
    String host = popString();
    ManagedEntityConfig config = (ManagedEntityConfig) stack.peek();
    config.setHost(host);
  }

  private void startWorkingDirectory(Attributes atts) {

  }

  private void endWorkingDirectory() {
    String workingDirectory = popString();
    ManagedEntityConfig config = (ManagedEntityConfig) stack.peek();
    config.setWorkingDirectory(workingDirectory);
  }

  private void startProductDirectory(Attributes atts) {

  }

  private void endProductDirectory() {
    String productDirectory = popString();
    ManagedEntityConfig config = (ManagedEntityConfig) stack.peek();
    config.setProductDirectory(productDirectory);
  }

  private void startSSL(Attributes atts) {
    DistributedSystemConfig config = (DistributedSystemConfig) stack.peek();
    config.setSSLEnabled(true);

    String authenticationRequired = atts.getValue(AUTHENTICATION_REQUIRED);
    config.setSSLAuthenticationRequired(Boolean.parseBoolean(authenticationRequired));
  }

  private void endSSL() {

  }

  private void startProtocols(Attributes atts) {

  }

  private void endProtocols() {
    String protocols = popString();
    DistributedSystemConfig config = (DistributedSystemConfig) stack.peek();
    config.setSSLProtocols(protocols);
  }

  private void startCiphers(Attributes atts) {

  }

  private void endCiphers() {
    String ciphers = popString();
    DistributedSystemConfig config = (DistributedSystemConfig) stack.peek();
    config.setSSLCiphers(ciphers);
  }

  private void startProperty(Attributes atts) {

  }

  private void endProperty() {
    String value = popString();
    String key = popString();
    DistributedSystemConfig config = (DistributedSystemConfig) stack.peek();
    config.addSSLProperty(key, value);
  }

  private void startKey(Attributes atts) {

  }

  private void endKey() {
    String key = popString();
    stack.push(key);
  }

  private void startValue(Attributes atts) {

  }

  private void endValue() {
    String value = popString();
    stack.push(value);
  }

  private void startCacheServer(Attributes atts) {
    DistributedSystemConfig config = (DistributedSystemConfig) stack.peek();
    CacheServerConfig server = config.createCacheServerConfig();
    stack.push(server);
  }

  private void endCacheServer() {
    /* CacheServerConfig server = (CacheServerConfig) */ stack.pop();
  }

  private void startClassPath(Attributes atts) {

  }

  private void endClassPath() {
    String classpath = popString();
    CacheServerConfig server = (CacheServerConfig) stack.peek();
    server.setClassPath(classpath);
  }

  /**
   * Pops a <code>String</code> off of the stack.
   */
  private String popString() {
    Object o = stack.pop();

    if (o instanceof StringBuilder) {
      StringBuilder sb = (StringBuilder) o;
      return sb.toString();

    } else {
      return (String) o;
    }
  }

  /**
   * Long strings in XML files may generate multiple <code>characters</code> callbacks. Coalesce
   * multiple callbacks into one big string by using a <code>StringBuilder</code>. See bug 32122.
   */
  @Override
  public void characters(char[] ch, int start, int length) throws SAXException {

    Object top = stack.peek();

    StringBuilder sb;
    if (top instanceof StringBuilder) {
      sb = (StringBuilder) top;

    } else {
      sb = new StringBuilder();
      stack.push(sb);
    }

    sb.append(ch, start, length);
  }

  ////////// Inherited methods that don't do anything //////////

  @Override
  public void setDocumentLocator(Locator locator) {}

  @Override
  public void startDocument() throws SAXException {}

  @Override
  public void endDocument() throws SAXException {}

  @Override
  public void startPrefixMapping(String prefix, String uri) throws SAXException {}

  @Override
  public void endPrefixMapping(String prefix) throws SAXException {}

  @Override
  public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {}

  @Override
  public void processingInstruction(String target, String data) throws SAXException {}

  @Override
  public void skippedEntity(String name) throws SAXException {}

  /////////////////////// Inner Classes ///////////////////////

  /**
   * Class that delegates all of the methods of a {@link DefaultHandler} to a
   * {@link ManagedEntityConfigXmlParser} that implements all of the methods of
   * <code>DefaultHandler</code>, but <B>is not</B> a <code>DefaultHandler</code>.
   */
  static class DefaultHandlerDelegate extends DefaultHandler {
    /**
     * The <code>ManagedEntityConfigXmlParser</code> that does the real work
     */
    private final ManagedEntityConfigXmlParser handler;

    /**
     * Creates a new <code>DefaultHandlerDelegate</code> that delegates to the given
     * <code>ManagedEntityConfigXmlParser</code>.
     */
    public DefaultHandlerDelegate(ManagedEntityConfigXmlParser handler) {
      this.handler = handler;
    }

    @Override
    public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
      return handler.resolveEntity(publicId, systemId);
    }

    @Override
    public void setDocumentLocator(Locator locator) {
      handler.setDocumentLocator(locator);
    }

    @Override
    public void startDocument() throws SAXException {
      handler.startDocument();
    }

    @Override
    public void endDocument() throws SAXException {
      handler.endDocument();
    }

    @Override
    public void startPrefixMapping(String prefix, String uri) throws SAXException {
      handler.startPrefixMapping(prefix, uri);
    }

    @Override
    public void endPrefixMapping(String prefix) throws SAXException {
      handler.endPrefixMapping(prefix);
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes)
        throws SAXException {
      handler.startElement(uri, localName, qName, attributes);
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
      handler.endElement(uri, localName, qName);
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
      handler.characters(ch, start, length);
    }

    @Override
    public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
      handler.ignorableWhitespace(ch, start, length);
    }

    @Override
    public void processingInstruction(String target, String data) throws SAXException {
      handler.processingInstruction(target, data);
    }

    @Override
    public void skippedEntity(String name) throws SAXException {
      handler.skippedEntity(name);
    }

    @Override
    public void warning(SAXParseException e) throws SAXException {
      handler.warning(e);
    }

    @Override
    public void error(SAXParseException e) throws SAXException {
      handler.error(e);
    }

    @Override
    public void fatalError(SAXParseException e) throws SAXException {
      handler.fatalError(e);
    }
  }

}
