/**
 *  Copyright © 2020-2025, Luis Andrés Lange <https://javacomm.net>
 *
 *  This Source Code Form is subject to the terms of the Mozilla Public
 *  License, v. 2.0. If a copy of the MPL was not distributed with this
 *  file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 *  ----------------------------------------------------------------------------
 *
 *  Exhibit B - "Incompatible With Secondary Licenses" Notice
 *
 *  This Source Code Form is "Incompatible With Secondary Licenses",
 *  as defined by the Mozilla Public License, v. 2.0.
 *
 *  In short:
 *  - This file may be used, modified, and distributed under MPL 2.0 only.
 *  - It may NOT be relicensed under GPL, LGPL, AGPL, or any other Secondary License.
 *
 *  Rationale:
 *  - Ensures that the code remains MPL-2.0.
 *  - Avoids legal conflicts with GPL-licensed libraries (e.g., VideoLAN).
 *  - Maximizes usability for commercial and security-critical applications.
 *
 */
package net.javacomm.client.environment;

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.PropertyException;
import jakarta.xml.bind.Unmarshaller;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.xml.sax.SAXException;
import net.javacomm.client.config.schema.Config;
import net.javacomm.client.config.schema.ObjectFactory;



/**
 * Lies- und Schreibe Konfigurationsdateien für die Benutzeranmeldung.
 * 
 * @author llange
 *
 */
public class Configfile {

  /**
   * Das verwendete Default-Schema liegt im angegebenen <i>package</i>
   * {@link org.nexuswob.properties}.
   */
  public final static String SCHEMAFILE = "/net/javacomm/client/config/schema/config.xsd";
  private final Logger log = LogManager.getLogger(Configfile.class);
  private final String SCHEMA_PATH = "net.javacomm.client.config.schema";
  private Path propertyfile;
  private volatile Schema propertyschema;

  /**
   * 
   * F�r das Lesen und Schreiben von Konfigurationsdateien wird das Default-Schema
   * <i>properties.xsd</i> verwendet.
   * 
   * 
   * @param path
   *             eine Datei mit vollständiger Pfadangabe
   * @throws SAXException
   */
  public Configfile(Path path) throws SAXException {
    this(path, null);
  }



  /**
   *
   * Die Benutzeranmeldung.
   * 
   * 
   * @param file
   *               XML-Datei
   * @param schema
   *               die Schemadatei beschreibt die Struktur der Anmeldedatei
   * @throws SAXException
   *                      die Schemadatei ist nicht vorhanden oder fehlerhaft
   */
  public Configfile(Path file, Path schema) throws SAXException {
    if (file == null) throw new IllegalArgumentException("file is null");
    propertyfile = file;
    init(schema);
  }



  private void init(Path schemafile) throws SAXException {
    SchemaFactory schemafactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    if (schemafile == null) {
      Class<? extends Configfile> clazz = getClass();
      // URL url = clazz.getResource(SCHEMAFILE);
      // diagschema = schemafactory.newSchema(url);
      StreamSource source = new StreamSource(clazz.getResourceAsStream(SCHEMAFILE));
      propertyschema = schemafactory.newSchema(source);
    }
    else {
      StreamSource source = new StreamSource(schemafile.toFile());
      propertyschema = schemafactory.newSchema(source);
    }
  }



  /**
   * Die Anmeldedatei als {@link File} Object.
   * 
   * @return das {@link Path} Object aus dem Konstruktor
   */
  public Path getPath() {
    return propertyfile;
  }



  /**
   * 
   * Dieses Schema wurde zum Lesen und Schreiben verwendet.
   * 
   * @return das Schema
   */
  public synchronized Schema getSchema() {
    return propertyschema;
  }



  /**
   * Eine Konfigurationsdatei wird gelesen.
   * 
   * 
   * @return der Wurzelknoten Properties
   * @throws JAXBException
   * @throws IOException
   */
  public Config read() throws JAXBException, IOException {
    Config root = null;
    try(BufferedReader inBuffer = Files.newBufferedReader(getPath(), Charset.forName("UTF-8"));) {
      JAXBContext jc = JAXBContext.newInstance(SCHEMA_PATH);
      Unmarshaller unmarshaller = jc.createUnmarshaller();
      unmarshaller.setSchema(getSchema());
      StreamSource source = new StreamSource(inBuffer);
      JAXBElement<Config> jaxbCollection = unmarshaller.unmarshal(source, Config.class);
      root = jaxbCollection.getValue();
    }
    return root;
  }



  /**
   * Eine Konfigurationsdatei wird geschrieben.
   * 
   * @param root
   *             der Wurzelknoten
   * 
   * @throws FileNotFoundException
   *                                  die Konfigurationsdatei konnte nicht
   *                                  geschrieben werden
   * @throws JAXBException
   *                                  die Konfigurationsdatei hat einen
   *                                  Schemafehler
   * @throws IOException
   * @throws IllegalArgumentException
   *                                  root is null
   */
  public synchronized void write(Config root) throws JAXBException, IOException {
    if (root == null) throw new IllegalArgumentException("root is null");
    Charset charset = Charset.forName("UTF-8");
    try(BufferedWriter buferredWriter = Files.newBufferedWriter(getPath(), charset);) {
      JAXBContext jc = JAXBContext.newInstance(SCHEMA_PATH);
      Marshaller marshaller = jc.createMarshaller();
      marshaller.setSchema(getSchema());
      ObjectFactory factory = new ObjectFactory();
      JAXBElement<Config> newroot = factory.createConfig(root);
      try {
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setProperty(Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION, "config.xsd");
      }
      catch (PropertyException e) {
        if (log.isDebugEnabled()) {
          log.debug(e.getMessage());
        }
      }
      marshaller.marshal(newroot, buferredWriter);
    }
  }

}
