/*
 * Copyright (C) 2007-2024 C# Port
 * Original Copyright (C) 2007-2018 Siemens AG
 *
 * This program 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, or
 * (at your option) any later version.
 */
using V2GDecoderNetFx.EXI;
using System.Xml;
namespace V2GDecoderNetFx.V2G
{
    /// 
    /// EXI Encoder for converting XML to EXI binary data
    /// 
    public class EXIEncoder
    {
        private readonly EXIConfig _config;
        public EXIEncoder(EXIConfig config = null)
        {
            _config = config ?? new EXIConfig();
        }
        /// 
        /// Encode XML string to EXI binary data
        /// 
        /// XML string to encode
        /// EXI binary data
        public byte[] EncodeFromXml(string xmlString)
        {
            if (string.IsNullOrEmpty(xmlString))
                throw new ArgumentException("XML string cannot be null or empty", nameof(xmlString));
            var xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xmlString);
            
            return EncodeFromXmlDocument(xmlDoc);
        }
        /// 
        /// Encode XmlDocument to EXI binary data
        /// 
        /// XmlDocument to encode
        /// EXI binary data
        public byte[] EncodeFromXmlDocument(XmlDocument xmlDoc)
        {
            if (xmlDoc == null)
                throw new ArgumentNullException(nameof(xmlDoc));
            var outputStream = new BitOutputStream();
            
            try
            {
                // Write EXI header
                WriteHeader(outputStream);
                
                // Encode document
                EncodeDocument(xmlDoc, outputStream);
                
                return outputStream.ToArray();
            }
            catch (EXIException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new EXIException(EXIErrorCodes.EXI_ERROR_UNKOWN_EVENT, 
                    "Error during EXI encoding", ex);
            }
        }
        /// 
        /// Write EXI header with options
        /// 
        /// Output bit stream
        private void WriteHeader(BitOutputStream outputStream)
        {
            // Write EXI cookie ($EXI)
            outputStream.WriteBytes(new byte[] { (byte)'$', (byte)'E', (byte)'X', (byte)'I' });
            
            // Format version (4 bits) - currently 0
            outputStream.WriteBits(0, 4);
            
            // Options presence flag (1 bit) - false for simplicity
            outputStream.WriteBit(0);
            
            // Align to byte boundary
            outputStream.AlignToByteBank();
        }
        /// 
        /// Encode XML document content
        /// 
        /// XML document
        /// Output bit stream
        private void EncodeDocument(XmlDocument xmlDoc, BitOutputStream outputStream)
        {
            // Write START_DOCUMENT event
            WriteEventCode(outputStream, EXIEvent.START_DOCUMENT);
            
            // Encode root element and its children
            if (xmlDoc.DocumentElement != null)
            {
                EncodeElement(xmlDoc.DocumentElement, outputStream);
            }
            
            // Write END_DOCUMENT event
            WriteEventCode(outputStream, EXIEvent.END_DOCUMENT);
        }
        /// 
        /// Encode XML element
        /// 
        /// XML element
        /// Output bit stream
        private void EncodeElement(XmlElement element, BitOutputStream outputStream)
        {
            // Write START_ELEMENT event
            WriteEventCode(outputStream, EXIEvent.START_ELEMENT);
            
            // Write element name (simplified - in real implementation would use string tables)
            WriteElementName(outputStream, element.Name);
            
            // Encode attributes
            EncodeAttributes(element, outputStream);
            
            // Encode child nodes
            foreach (XmlNode child in element.ChildNodes)
            {
                switch (child.NodeType)
                {
                    case XmlNodeType.Element:
                        EncodeElement((XmlElement)child, outputStream);
                        break;
                        
                    case XmlNodeType.Text:
                    case XmlNodeType.CDATA:
                        EncodeTextContent(child.Value ?? string.Empty, outputStream);
                        break;
                        
                    case XmlNodeType.Comment:
                        if (_config != null) // Preserve comments if configured
                        {
                            // Skip for simplicity
                        }
                        break;
                }
            }
            
            // Write END_ELEMENT event
            WriteEventCode(outputStream, EXIEvent.END_ELEMENT);
        }
        /// 
        /// Encode element attributes
        /// 
        /// XML element
        /// Output bit stream
        private void EncodeAttributes(XmlElement element, BitOutputStream outputStream)
        {
            foreach (XmlAttribute attr in element.Attributes)
            {
                // Write ATTRIBUTE event
                WriteEventCode(outputStream, EXIEvent.ATTRIBUTE);
                
                // Write attribute name and value (simplified)
                WriteAttributeName(outputStream, attr.Name);
                WriteAttributeValue(outputStream, attr.Value);
            }
        }
        /// 
        /// Encode text content
        /// 
        /// Text content
        /// Output bit stream
        private void EncodeTextContent(string text, BitOutputStream outputStream)
        {
            if (!string.IsNullOrEmpty(text))
            {
                // Write CHARACTERS event
                WriteEventCode(outputStream, EXIEvent.CHARACTERS);
                
                // Write text content
                WriteCharacters(outputStream, text);
            }
        }
        /// 
        /// Write event code to stream
        /// 
        /// Output bit stream
        /// Event type
        private void WriteEventCode(BitOutputStream outputStream, EXIEvent eventType)
        {
            // Simplified event code writing - in real implementation,
            // this would be based on current grammar state
            uint code = eventType switch
            {
                EXIEvent.START_DOCUMENT => 0,
                EXIEvent.START_ELEMENT => 0,
                EXIEvent.END_ELEMENT => 1,
                EXIEvent.CHARACTERS => 2,
                EXIEvent.ATTRIBUTE => 3,
                EXIEvent.END_DOCUMENT => 3,
                _ => 0
            };
            
            outputStream.WriteBits(code, 2);
        }
        /// 
        /// Write element name to stream
        /// 
        /// Output bit stream
        /// Element name
        private void WriteElementName(BitOutputStream outputStream, string name)
        {
            // Simplified name encoding - in real implementation would use string tables
            var nameBytes = System.Text.Encoding.UTF8.GetBytes(name);
            outputStream.WriteUnsignedInteger((uint)nameBytes.Length);
            outputStream.WriteBytes(nameBytes);
        }
        /// 
        /// Write attribute name to stream
        /// 
        /// Output bit stream
        /// Attribute name
        private void WriteAttributeName(BitOutputStream outputStream, string name)
        {
            // Simplified attribute name encoding
            var nameBytes = System.Text.Encoding.UTF8.GetBytes(name);
            outputStream.WriteUnsignedInteger((uint)nameBytes.Length);
            outputStream.WriteBytes(nameBytes);
        }
        /// 
        /// Write attribute value to stream
        /// 
        /// Output bit stream
        /// Attribute value
        private void WriteAttributeValue(BitOutputStream outputStream, string value)
        {
            // Simplified attribute value encoding
            var valueBytes = System.Text.Encoding.UTF8.GetBytes(value ?? string.Empty);
            outputStream.WriteUnsignedInteger((uint)valueBytes.Length);
            outputStream.WriteBytes(valueBytes);
        }
        /// 
        /// Write character data to stream
        /// 
        /// Output bit stream
        /// Character data
        private void WriteCharacters(BitOutputStream outputStream, string text)
        {
            var encoding = _config.Strings switch
            {
                EXIConfig.StringRepresentation.ASCII => System.Text.Encoding.ASCII,
                EXIConfig.StringRepresentation.UCS => System.Text.Encoding.UTF8,
                _ => System.Text.Encoding.UTF8
            };
            
            var textBytes = encoding.GetBytes(text);
            outputStream.WriteUnsignedInteger((uint)textBytes.Length);
            outputStream.WriteBytes(textBytes);
        }
    }
}