/*
 * 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.activemq.artemis.dto;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.util.StreamReaderDelegate;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URI;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class XmlUtil {

   /**
    * Changes ${property} with values from a properties object
    */
   static class PropertiesFilter extends StreamReaderDelegate {

      static final Pattern pattern = Pattern.compile("\\$\\{([^\\}]+)\\}");
      private final Properties props;

      PropertiesFilter(XMLStreamReader parent, Properties props) {
         super(parent);
         this.props = props;
      }

      @Override
      public String getAttributeValue(int index) {
         return filter(super.getAttributeValue(index));
      }

      public String filter(String str) {
         int start = 0;
         while (true) {
            Matcher matcher = pattern.matcher(str);
            if (!matcher.find(start)) {
               break;
            }
            String group = matcher.group(1);
            String property = props.getProperty(group);
            if (property != null) {
               str = matcher.replaceFirst(Matcher.quoteReplacement(property));
            } else {
               start = matcher.end();
            }
         }
         return str;
      }

   }

   private static final XMLInputFactory factory = XMLInputFactory.newInstance();

   public static <T> T decode(Class<T> clazz, File configuration) throws Exception {
      return decode(clazz, configuration, null, null, null);
   }

   /**
    * We offer parameters for artemisInstance and artemisHome as they could be coming from the CLI or Maven Plugin
    */
   public static <T> T decode(Class<T> clazz,
                              File configuration,
                              String artemisHome,
                              String artemisInstance,
                              URI artemisURIInstance) throws Exception {
      JAXBContext jaxbContext = JAXBContext.newInstance("org.apache.activemq.artemis.dto");

      Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
      SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
      sf.setFeature("http://apache.org/xml/features/validation/schema-full-checking", false);
      InputStream xsdStream = XmlUtil.class.getClassLoader().getResourceAsStream("org.apache.activemq/dto/activemq.xsd");
      StreamSource xsdSource = new StreamSource(xsdStream);
      Schema schema = sf.newSchema(xsdSource);
      unmarshaller.setSchema(schema);

      Properties props = new Properties(System.getProperties());
      if (artemisHome != null) {
         props.put("artemis.home", artemisHome);
      }

      if (artemisInstance != null) {
         props.put("artemis.instance", artemisInstance);
      }

      if (artemisURIInstance != null) {
         props.put("artemis.URI.instance", artemisURIInstance.toString());
      }

      XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream(configuration));

      reader = new PropertiesFilter(reader, props);

      return clazz.cast(unmarshaller.unmarshal(reader));
   }

}
