/*
 * XmlOps.cs --
 *
 * Copyright (c) 2007-2012 by Joe Mistachkin.  All rights reserved.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: $
 */

using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Schema;

#if SERIALIZATION
using System.Xml.Serialization;
#endif

using Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Constants;

namespace Eagle._Components.Private
{
    [ObjectId("dc088aa2-7481-4313-94dc-4809fb31eb1d")]
    internal static class XmlOps
    {
        #region Private Constants
        //
        // NOTE: This string is used to detect if a given string looks like the
        //       start of an XML document.  This is not designed to be a "perfect"
        //       detection mechanism; however, it will work well enough for our
        //       purposes.
        //
        private static readonly string DocumentStart = "<?xml ";

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static readonly Encoding DefaultEncoding = Encoding.UTF8;
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Generic Xml Handling Methods
        public static bool LooksLikeDocument(
            string text
            )
        {
            return (text != null) && text.StartsWith(
                DocumentStart, StringOps.SystemStringComparisonType);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static ReturnCode GetEncoding(
            XmlDocument document,
            ref Encoding encoding,
            ref Result error
            )
        {
            if (document != null)
            {
                try
                {
                    XmlDeclaration declaration = (XmlDeclaration)document.FirstChild;

                    if (declaration != null)
                    {
                        string encodingName = declaration.Encoding;

                        if (!String.IsNullOrEmpty(encodingName))
                            encoding = Encoding.GetEncoding(encodingName);
                        else
                            encoding = Interpreter.DefaultEncoding;

                        return ReturnCode.Ok;
                    }
                    else
                    {
                        error = "invalid xml declaration";
                    }
                }
                catch (Exception e)
                {
                    error = e;
                }
            }
            else
            {
                error = "invalid xml document";
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static ReturnCode LoadString(
            string xml,
            ref XmlDocument document,
            ref Result error
            )
        {
            if (xml != null)
            {
                try
                {
                    if (document == null)
                        document = new XmlDocument();

                    document.LoadXml(xml);

                    return ReturnCode.Ok;
                }
                catch (Exception e)
                {
                    error = e;
                }
            }
            else
            {
                error = "invalid xml";
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Dead Code
#if DEAD_CODE
        public static ReturnCode LoadFile(
            string fileName,
            ref XmlDocument document,
            ref Result error
            )
        {
            if (!String.IsNullOrEmpty(fileName))
            {
                bool remoteUri = PathOps.IsRemoteUri(fileName);

                if (remoteUri || File.Exists(fileName))
                {
                    try
                    {
                        document = new XmlDocument();
                        document.Load(fileName);

                        return ReturnCode.Ok;
                    }
                    catch (Exception e)
                    {
                        error = e;
                    }
                }
                else
                {
                    error = String.Format(
                        "couldn't read file \"{0}\": no such file or directory",
                        fileName);
                }
            }
            else
            {
                error = "invalid file name";
            }

            return ReturnCode.Error;
        }
#endif
        #endregion
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Xml Script Block Methods
        #region Private Xml Script Block Methods
        private static ReturnCode GetSchemaStream(
            string schemaXml,
            ref Stream stream,
            ref Result error
            )
        {
            try
            {
                stream = new MemoryStream(
                    DefaultEncoding.GetBytes(schemaXml));

                return ReturnCode.Ok;
            }
            catch (Exception e)
            {
                error = e;
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static ReturnCode GetSchemaStream(
            Assembly assembly,
            string resourceName,
            ref Stream stream,
            ref Result error
            )
        {
            try
            {
                if (assembly == null)
                    assembly = GlobalState.GetAssembly();

                if (resourceName == null)
                    resourceName = Xml.SchemaResourceName;

                stream = AssemblyOps.GetResourceStream(
                    assembly, resourceName, ref error);

                if (stream != null)
                    return ReturnCode.Ok;
            }
            catch (Exception e)
            {
                error = e;
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static ReturnCode GetNamespaceManager(
            string namespaceName,
            Uri namespaceUri,
            XmlNameTable nameTable,
            ref XmlNamespaceManager namespaceManager,
            ref Result error
            )
        {
            if (nameTable != null)
            {
                if (namespaceName != null)
                {
                    if (namespaceUri != null)
                    {
                        try
                        {
                            namespaceManager = new XmlNamespaceManager(nameTable);

                            namespaceManager.AddNamespace(
                                namespaceName, namespaceUri.ToString());

                            return ReturnCode.Ok;
                        }
                        catch (Exception e)
                        {
                            error = e;
                        }
                    }
                    else
                    {
                        error = "invalid xml namespace uri";
                    }
                }
                else
                {
                    error = "invalid xml namespace name";
                }
            }
            else
            {
                error = "invalid xml name table";
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static ReturnCode GetNamespaceManager(
            XmlNameTable nameTable,
            ref XmlNamespaceManager namespaceManager,
            ref Result error
            )
        {
            return GetNamespaceManager(
                Xml.NamespaceName, Xml.NamespaceUri, nameTable,
                ref namespaceManager, ref error);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static ReturnCode Validate(
            XmlSchema schema,
            XmlDocument document,
            ref Result error
            )
        {
            if (document != null)
            {
                if (schema != null)
                {
                    try
                    {
                        document.Schemas.Add(schema); /* throw */
                        document.Validate(null); /* throw */

                        return ReturnCode.Ok;
                    }
                    catch (Exception e)
                    {
                        error = e;
                    }
                }
                else
                {
                    error = "invalid xml schema";
                }
            }
            else
            {
                error = "invalid xml document";
            }

            return ReturnCode.Error;
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Public Xml Script Block Methods
        public static ReturnCode Validate(
            string schemaXml,
            XmlDocument document,
            ref Result error
            )
        {
            if (document != null)
            {
                Stream stream = null;

                try
                {
                    if (GetSchemaStream(schemaXml,
                            ref stream, ref error) == ReturnCode.Ok)
                    {
                        try
                        {
                            return Validate(XmlSchema.Read(stream, null),
                                document, ref error);
                        }
                        catch (Exception e)
                        {
                            error = e;
                        }
                    }
                }
                finally
                {
                    if (stream != null)
                    {
                        stream.Close();
                        stream = null;
                    }
                }
            }
            else
            {
                error = "invalid xml document";
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static ReturnCode Validate(
            Assembly assembly,
            string resourceName,
            XmlDocument document,
            ref Result error
            )
        {
            if (document != null)
            {
                Stream stream = null;

                try
                {
                    if (GetSchemaStream(assembly, resourceName,
                            ref stream, ref error) == ReturnCode.Ok)
                    {
                        try
                        {
                            return Validate(XmlSchema.Read(stream, null),
                                document, ref error);
                        }
                        catch (Exception e)
                        {
                            error = e;
                        }
                    }
                }
                finally
                {
                    if (stream != null)
                    {
                        stream.Close();
                        stream = null;
                    }
                }
            }
            else
            {
                error = "invalid xml document";
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static ReturnCode GetNodeList(
            XmlDocument document,
            ref XmlNodeList nodeList,
            ref Result error
            )
        {
            if (document != null)
            {
                XmlNamespaceManager namespaceManager = null;

                if (GetNamespaceManager(document.NameTable,
                        ref namespaceManager, ref error) == ReturnCode.Ok)
                {
                    foreach (string xpath in Xml.XPathList)
                    {
                        if (!String.IsNullOrEmpty(xpath))
                        {
                            try
                            {
                                nodeList = document.SelectNodes(xpath, namespaceManager);

                                if ((nodeList != null) && (nodeList.Count > 0))
                                    return ReturnCode.Ok;
                            }
                            catch (Exception e)
                            {
                                error = e;
                            }
                        }
                    }

                    error = String.Format(
                        "{0} xml nodes not found",
                        Xml.NamespaceName).TrimStart();
                }
            }
            else
            {
                error = "invalid xml document";
            }

            return ReturnCode.Error;
        }
        #endregion
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Serialization Methods
#if SERIALIZATION
        public static ReturnCode Serialize(
            object @object,
            Type type,
            XmlSerializerNamespaces serializerNamespaces,
            ref byte[] bytes,
            ref Result error
            )
        {
            if (@object != null)
            {
                if (type != null)
                {
                    if (bytes == null)
                    {
                        try
                        {
                            using (MemoryStream stream = new MemoryStream())
                            {
                                XmlSerializer serializer = new XmlSerializer(type);
                                serializer.Serialize(stream, @object, serializerNamespaces);
                                serializer = null;

                                bytes = stream.ToArray();
                            }

                            return ReturnCode.Ok;
                        }
                        catch (Exception e)
                        {
                            error = e;
                        }
                    }
                    else
                    {
                        error = "cannot overwrite valid byte array";
                    }
                }
                else
                {
                    error = "invalid type";
                }
            }
            else
            {
                error = "invalid object";
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static ReturnCode Serialize(
            object @object,
            Type type,
            XmlWriter writer,
            XmlSerializerNamespaces serializerNamespaces,
            ref Result error
            )
        {
            if (@object != null)
            {
                if (type != null)
                {
                    if (writer != null)
                    {
                        try
                        {
                            XmlSerializer serializer = new XmlSerializer(type);
                            serializer.Serialize(writer, @object, serializerNamespaces);
                            serializer = null;

                            return ReturnCode.Ok;
                        }
                        catch (Exception e)
                        {
                            error = e;
                        }
                    }
                    else
                    {
                        error = "invalid xml writer";
                    }
                }
                else
                {
                    error = "invalid type";
                }
            }
            else
            {
                error = "invalid object";
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static ReturnCode Deserialize(
            Type type,
            byte[] bytes,
            ref object @object,
            ref Result error
            )
        {
            if (type != null)
            {
                if (bytes != null)
                {
                    if (@object == null)
                    {
                        try
                        {
                            using (MemoryStream stream = new MemoryStream(bytes))
                            {
                                XmlSerializer serializer = new XmlSerializer(type);
                                @object = serializer.Deserialize(stream);
                                serializer = null;
                            }

                            return ReturnCode.Ok;
                        }
                        catch (Exception e)
                        {
                            error = e;
                        }
                    }
                    else
                    {
                        error = "cannot overwrite valid object";
                    }
                }
                else
                {
                    error = "invalid byte array";
                }
            }
            else
            {
                error = "invalid type";
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public static ReturnCode Deserialize(
            Type type,
            XmlReader reader,
            ref object @object,
            ref Result error
            )
        {
            if (type != null)
            {
                if (reader != null)
                {
                    if (@object == null)
                    {
                        try
                        {
                            XmlSerializer serializer = new XmlSerializer(type);
                            @object = serializer.Deserialize(reader);
                            serializer = null;

                            return ReturnCode.Ok;
                        }
                        catch (Exception e)
                        {
                            error = e;
                        }
                    }
                    else
                    {
                        error = "cannot overwrite valid object";
                    }
                }
                else
                {
                    error = "invalid xml reader";
                }
            }
            else
            {
                error = "invalid type";
            }

            return ReturnCode.Error;
        }
#endif
        #endregion
    }
}
