/**
 *  Copyright © 2025, Luis Andrés Lange <https://javacomm.net>
 *
 *  Previously released under Apache License 2.0; now licensed under MPL 2.0.
 *
 *  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.file;

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.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStreamWriter;
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.config.Context;



/**
 * 
 * Ein Wrapper für das Bearbeiten der Konfigurationsdatei.
 * 
 * @author llange
 *
 */
public class ContextFile {

  private final Logger log = LogManager.getLogger(ContextFile.class);
  private final String SCHEMA_PATH = "net.javacomm.config";
  private final int bufferSize = 65535;
  /**
   * Das verwendete Default-Schema liegt im angegebenen <i>package</i>
   * {@link net.javacomm.file}.
   */
  public final static String SCHEMAFILE = "/net/javacomm/config/crusaders.xsd";
  private Path crusadersfile;
  private Schema crusadersschema;

  /**
   * 
   * @param file
   *               die Konfigurationsdatei
   * @param schema
   *               das Schema für die Konfigurationsdatei
   * @throws SAXException
   *                      beim Parsen der Schemadatei ist ein Fehler aufgetreten
   */
  public ContextFile(Path file, Path schema) throws SAXException {
    if (file == null) throw new IllegalArgumentException("file is null");
    crusadersfile = file;
    init(schema);
  }



  /**
   * 
   * Für das Lesen und Schreiben der Konfigurationsdatei wird das Default-Schema
   * <i>crusaders.xsd</i> verwendet.
   * 
   * @param file
   *             die Konfigurationsdatei
   * @throws SAXException
   *                      beim Parsen der Schemadatei ist ein Fehler aufgetreten
   */
  public ContextFile(Path file) throws SAXException {
    this(file, null);
  }



  /**
   * 
   * 
   * @param schemafile
   * @throws SAXException
   */
  private void init(Path schemafile) throws SAXException {
    SchemaFactory schemafactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    if (schemafile == null) {
      Class<? extends ContextFile> clazz = getClass();
      log.info("SCHEMAFILE -----> " + SCHEMAFILE);
      StreamSource source = new StreamSource(clazz.getResourceAsStream(SCHEMAFILE));
      crusadersschema = schemafactory.newSchema(source);
    }
    else {
      try(BufferedReader reader = Files.newBufferedReader(schemafile)) {
        StreamSource source = new StreamSource(reader);
        crusadersschema = schemafactory.newSchema(source);
      }
      catch (IOException e) {
        log.info(e);
      }
    }
  }



  /**
   * Die Konfigurationsdatei
   * 
   * @return connection.xml
   */
  public Path getFile() {
    return crusadersfile;
  }



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



  /**
   * Die Konfigurationsdatei wird gelesen und im Objektbaum
   * {@link org.nexuswob.config.Context} abgelegt.
   * 
   * 
   * @return der Wurzelknoten Context oder {@code null}
   * @throws JAXBException
   */
  public Context getContext() throws JAXBException {
    Context ctx = null;
    BufferedReader reader = null;
    try {
      reader = Files.newBufferedReader(getFile());
      if (log.isDebugEnabled()) log.debug(SCHEMA_PATH);
      JAXBContext jc = JAXBContext.newInstance(SCHEMA_PATH);
      Unmarshaller unmarshaller = jc.createUnmarshaller();
      unmarshaller.setSchema(getSchema());
      StreamSource source = new StreamSource(reader);
      JAXBElement<Context> root = unmarshaller.unmarshal(source, Context.class);
      ctx = root.getValue();
    }
    catch (IOException e) {
      log.error(e.getMessage(), e.getCause());
    }
    finally {
      try {
        if (reader != null) reader.close();
      }
      catch (IOException e) {}
    }
    return ctx;
  }



  /**
   * Die Konfigurationsdatei mit den Datenbankeinträgen für den Server wird
   * geschrieben. Das Schema ist <i>crusaders.xsd</i>
   * 
   * @param root
   *             der Wurzelknoten
   * @throws FileNotFoundException
   *                               die Konfigurationsdatei kann nicht geschrieben
   *                               werden
   * @throws JAXBException
   *                               in den meisten F�llen eine Schemaverletzung
   */
  public void write(Context root) throws FileNotFoundException, JAXBException {
    BufferedOutputStream outBuffer = null;
    OutputStreamWriter outwriter = null;
    Charset charset = Charset.forName("UTF-8");
    try {
      outBuffer = new BufferedOutputStream(Files.newOutputStream(getFile()), bufferSize);
      outwriter = new OutputStreamWriter(outBuffer, charset);
      if (log.isDebugEnabled()) log.debug(SCHEMA_PATH);
      JAXBContext jc = JAXBContext.newInstance(SCHEMA_PATH);
      Marshaller marshaller = jc.createMarshaller();
      marshaller.setSchema(getSchema());
      try {
        marshaller.setProperty("com.sun.xml.bind.indentString", "    ");
      }
      catch (PropertyException e) {
        if (log.isDebugEnabled()) log.debug(e.getMessage());
      }
      marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
      marshaller.setProperty(Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION, "crusaders.xsd");
      marshaller.marshal(root, outwriter);
    }
    catch (IOException e1) {
      log.error(e1.getMessage(), e1.getCause());
    }
    finally {
      try {
        if (outwriter != null) {
          outwriter.flush();
        }
      }
      catch (IOException e) {}
      try {
        if (outwriter != null) {
          outwriter.close();
        }
      }
      catch (IOException e) {}
    }
  }

}
