using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Diagnostics;
using System.Reflection;
using System.Security.Cryptography;
using Aitex.Core.RT.Log;
namespace Aitex.Core.Util
{
    /// 
    /// This class provides a set of static APIs that add digital signature to Xml files and verify if Xml files
    /// or Xml memory streams have valid signature.
    /// 
    public static class FileSigner
    {
        /* This API didn't pass unit test. Will be uncommented in the future.
        /// 
        /// Checks to see if the memory stream is a valid xml stream with valid signature.
        /// 
        /// 
        /// False if the stream is empty or null, or embedded signature doesn't match, or there is no the signature.
        public static bool IsValid(Stream stream)
        {
            bool retVal = false;
            try
            {
                Logger.LogTrace(MethodBase.GetCurrentMethod(), Logger.TraceEntry);
                XmlDocument doc = new XmlDocument();
                doc.Load(stream);
                Debug.Assert(doc != null && doc.DocumentElement != null);
                // Get root element
                XmlElement elemRoot = doc.DocumentElement;
                // Get signature element
                XmlElement elemSignature = elemRoot["Signature"];
                if (elemSignature == null)
                {
                    return false; // The stream was not signed.
                }
                // Remove signature element from document
                elemRoot.RemoveChild(elemSignature);
                // Calculate hash code from file (after removing Signature element)
                UnicodeEncoding ue = new UnicodeEncoding();
                SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
                char[] innerXMLArray = elemRoot.InnerXml.ToCharArray();
                byte[] bytes = ue.GetBytes(innerXMLArray);
                // byte[] signature = sha1.ComputeHash(ue.GetBytes(elemRoot.InnerXml));
                byte[] signature = sha1.ComputeHash(bytes);
                string strSignature = Convert.ToBase64String(signature);
                // Add signature back to document
                elemRoot.AppendChild(elemSignature);
                // Compare embedded signature to calculated value
                if (elemSignature.InnerText == strSignature)
                {
                    retVal = true;
                }
            }
            catch (System.Exception ex)
            {
                retVal = false;
                Logger.LogError(MethodBase.GetCurrentMethod(), ex);
                throw;
            }
            finally
            {
                Logger.LogTrace(MethodBase.GetCurrentMethod(), Logger.TraceExit);
            }
            return retVal;
        }
        */
        /// 
        /// Checks to see if the file is a valid xml file with valid signature.
        /// 
        /// 
        /// False if the embedded signature doesn't match, or there is no the signature.
        public static bool IsValid(string fileName)
        {
            bool retVal = false;
            try
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(fileName);
                Debug.Assert(doc != null && doc.DocumentElement != null);
                // Get root element
                XmlElement elemRoot = doc.DocumentElement;
                // Get signature element
                XmlElement elemSignature = elemRoot["Signature"];
                if (elemSignature == null)
                {
                    return false; // The file was not signed.
                }
                // Remove signature element from document
                elemRoot.RemoveChild(elemSignature);
                // Calculate hash code from file (after removing Signature element)
                UnicodeEncoding ue = new UnicodeEncoding();
                SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
                byte[] signature = sha1.ComputeHash(ue.GetBytes(elemRoot.InnerXml));
                string strSignature = Convert.ToBase64String(signature);
                // Add signature back to document
                elemRoot.AppendChild(elemSignature);
                // Compare embedded signature to calculated value
                if (elemSignature.InnerText == strSignature)
                {
                    retVal = true;
                }
            }
            catch (System.Exception e)
            {
                retVal = false;
                LOG.Write(e);
                throw;
            }
            finally
            {
            }
            return retVal;
        }
        /* This API didn't pass unit test. Will be uncommented in the future.
        /// 
        /// Add signature to the end of the xml memory stream
        /// 
        /// 
        /// 
        public static Stream Sign(Stream stream)
        {
            try
            {
                Logger.LogTrace(MethodBase.GetCurrentMethod(), Logger.TraceEntry);
                XmlDocument doc = new XmlDocument();
                if (stream == null || stream.Length == 0)
                {
                    throw new InvalidOperationException("Empty stream, no XML file to Sign");
                }
                else
                {
                    doc.Load(stream);
                    XmlElement elemRoot = doc.DocumentElement;
                    // Remove any existing signature
                    XmlElement elemSignature = elemRoot["Signature"];
                    if (elemSignature != null)
                    {
                        elemRoot.RemoveChild(elemSignature);
                    }
                    // Calculate hash code (after removing Signature element)
                    UnicodeEncoding ue = new UnicodeEncoding();
                    SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
                    char[] innerXMLArray = elemRoot.InnerXml.ToCharArray();
                    byte[] bytes = ue.GetBytes(innerXMLArray);
                    // byte[] nSignature = sha1.ComputeHash(ue.GetBytes(elemRoot.InnerXml));
                    byte[] nSignature = sha1.ComputeHash(bytes);
                    string strSignature = Convert.ToBase64String(nSignature);
                    // Add signature to XML document
                    elemSignature = doc.CreateElement("Signature");
                    elemSignature.InnerText = strSignature;
                    elemRoot.AppendChild(elemSignature);
                    // memStream.Flush();
                    stream.Seek(0, SeekOrigin.Begin);
                    doc.Save(stream);
                    return stream;
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(MethodBase.GetCurrentMethod(), ex);
                throw;
            }
            finally
            {
                Logger.LogTrace(MethodBase.GetCurrentMethod(), Logger.TraceExit);
            }
        }
        */
        /// 
        /// Add signature to the end of the xml file. 
        /// 
        /// 
        public static void Sign(string fileName)
        {
            try
            {
                XmlDocument doc = new XmlDocument();
                bool writeable = true;
                if (File.Exists(fileName) && (File.GetAttributes(fileName) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                {
                    writeable = false;
                    File.SetAttributes(fileName, FileAttributes.Normal);
                }
                doc.Load(fileName);
                XmlElement elemRoot = doc.DocumentElement;
                // Remove any existing signature
                XmlElement elemSignature = elemRoot["Signature"];
                if (elemSignature != null)
                {
                    elemRoot.RemoveChild(elemSignature);
                }
                // Calculate hash code (after removing Signature element)
                UnicodeEncoding ue = new UnicodeEncoding();
                SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
                byte[] nSignature = sha1.ComputeHash(ue.GetBytes(elemRoot.InnerXml));
                string strSignature = Convert.ToBase64String(nSignature);
                // Add signature to XML document
                elemSignature = doc.CreateElement("Signature");
                elemSignature.InnerText = strSignature;
                elemRoot.AppendChild(elemSignature);
                doc.Save(fileName);
                if (!writeable)
                {
                    File.SetAttributes(fileName, FileAttributes.ReadOnly);
                }
            }
            catch (Exception ex)
            {
                LOG.Write(ex);
                throw;
            }
            finally
            {
            }
        }
    }
}