/******************************************************************************
 * Copyright (C) 2010-2016 CERN. All rights not expressly granted are reserved.
 *
 * This file is part of the CERN Control and Monitoring Platform 'C2MON'.
 * C2MON 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 3 of the license.
 *
 * C2MON 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 C2MON. If not, see <http://www.gnu.org/licenses/>.
 *****************************************************************************/
package cern.c2mon.shared.client.configuration;

import java.io.StringWriter;

import javax.annotation.PostConstruct;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import lombok.extern.slf4j.Slf4j;
import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import cern.c2mon.shared.util.parser.ParserException;
import cern.c2mon.shared.util.parser.SimpleXMLParser;

/**
 * JMSMessage to ConfigurationRequest conversion class. Also checks that
 * a replyTo is set on the message to send back a report.
 *
 * <p>Also contains the XML conversion methods.
 *
 * @author Mark Brightwell
 *
 */
@Slf4j
@Component
public class ConfigurationRequestConverter implements MessageConverter {

  /**
   * XML root element
   */
  public static final String CONFIGURATION_XML_ROOT = "ConfigurationRequest";

  /**
   * XML id attribute
   */
  public static final String CONFIGURATION_ID_ATTRIBUTE = "config-id";

  /**
   * Could remove this parser and use Java standard one.
   */
  private SimpleXMLParser parser;

  /**
   * Init method run on bean instantiation.
   * Initializes XML parser.
   */
  @PostConstruct
  public void init() {
    try {
      this.parser = new SimpleXMLParser();
    }
    catch (ParserConfigurationException e) {
      //should not happen: throw unchecked fatal error
      throw new RuntimeException("Error creating instance of SimpleXMLParser:", e);
    }
  }

  @Override
  public Object fromMessage(final Message message) throws JMSException, MessageConversionException {
    if (TextMessage.class.isAssignableFrom(message.getClass())) {
      try {
        Document doc = parser.parse(((TextMessage) message).getText());
        String docName = doc.getDocumentElement().getNodeName();
        if (docName.equals(CONFIGURATION_XML_ROOT)) {
          if (log.isDebugEnabled()) {
            log.debug("fromMessage() : Reconfiguration request received.");
          }
          return fromXML(doc.getDocumentElement());
        } else {
         log.error("Unrecognized XML message received on the reconfiguration request topic - is being ignored");
         log.error("  (unrecognized document root element is " + docName + ")");
         throw new MessageConversionException("Unrecognized XML message received.");
        }
      } catch (ParserException ex) {
        log.error("Exception caught in DOM parsing of incoming message: ", ex);
        //TODO may need to adjust encoding in next line (UTF-8 or whatever used by ActiveMQ)?
        log.error("Message was: " + ((TextMessage) message).getText());
        throw new MessageConversionException("Exception caught in DOM parsing on reconfiguration request message");
      }
    } else {
      StringBuffer str = new StringBuffer("fromMessage() : Unsupported message type(");
      str.append(message.getClass().getName());
      str.append(") : Message discarded.");
      log.error(str.toString());
      throw new MessageConversionException("Unsupported JMS message type received on configuration request connection.");
    }
  }

  @Override
  public Message toMessage(final Object configRequest, final Session session) throws JMSException, MessageConversionException {
    Message textMessage;
    if (configRequest instanceof ConfigurationRequest) {
      String xmlString;
      try {
        xmlString = toXML(((ConfigurationRequest) configRequest));
        textMessage = session.createTextMessage(xmlString);
        return textMessage;
      } catch (ParserConfigurationException ex) {
        log.error("Parser exception caught whilst encoding the ConfigurationRequest object; aborting request. ", ex);
        ex.printStackTrace();
        throw new MessageConversionException("ConfigurationRequest conversion failed.",  ex);
      } catch (TransformerException ex) {
        log.error("Transformer exception caught whilst encoding the ConfigurationRequest object; aborting request. ", ex);
        ex.printStackTrace();
        throw new MessageConversionException("ConfigurationRequest conversion failed.",  ex);
      }
    } else {
      log.error("ConfigurationRequestConverter is being called on the following type that is not supported: " + configRequest.getClass());
      throw new MessageConversionException("ConfigurationRequestConverter is being called on an unsupported type");
    }
  }

  /**
   * Create an XML string representation of the ProcessConnectionRequest object.
   * @throws ParserConfigurationException if exception occurs in XML parser
   * @throws TransformerException if exception occurs in XML parser
   * @param configurationRequest the request object to convert to XML
   * @return XML as String
   */
  public synchronized String toXML(final ConfigurationRequest configurationRequest) throws ParserConfigurationException, TransformerException {
    DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
    DocumentBuilder builder = builderFactory.newDocumentBuilder();
    Document dom = builder.newDocument();
    Element rootElt = dom.createElement(CONFIGURATION_XML_ROOT);
    rootElt.setAttribute(CONFIGURATION_ID_ATTRIBUTE, Integer.toString(configurationRequest.getConfigId()));
    dom.appendChild(rootElt);

    TransformerFactory transformerFactory = TransformerFactory.newInstance();
    Transformer transformer = transformerFactory.newTransformer();
    StringWriter writer = new StringWriter();
    Result result = new StreamResult(writer);
    Source source = new DOMSource(dom);
    transformer.transform(source, result);
    return writer.getBuffer().toString();

  }

  /**
   * Converts the top level DOM element of an XML document to
   * a ConfigurationRequest object.
   * @param domElement the top-level XML element ("ConfigurationRequest")
   * @return the request object
   */
  private static ConfigurationRequest fromXML(final Element domElement) {
    ConfigurationRequest result = null;

    if (domElement.getNodeName().equals(CONFIGURATION_XML_ROOT)) {
      String idStr = domElement.getAttribute(CONFIGURATION_ID_ATTRIBUTE);
      if (idStr == null || idStr.length() == 0) {
        throw new RuntimeException("Unable to read configuration request id: " + idStr);
      } else {
        result = new ConfigurationRequest(new Integer(idStr));
      }
      return result;
    } else {
      throw new MessageConversionException("Called the fromXML message on the wrong XML doc Element! - check your code as this should be checked first");
    }
  }
}