using System;
using System.Collections.Generic;
using ArchestrA.GRAccess;

namespace ZB.MOM.WW.GRAccess.Cli.GRAccess
{
    internal static class GRAccessQueryService
    {
        public static IReadOnlyList<GRAccessObjectInfo> QueryObjects(
            IGalaxy galaxy,
            GRAccessObjectKind kind,
            string pattern)
        {
            if (galaxy == null) throw new ArgumentNullException(nameof(galaxy));
            if (string.IsNullOrWhiteSpace(pattern)) pattern = "%";

            var results = new List<GRAccessObjectInfo>();

            if (kind == GRAccessObjectKind.All || kind == GRAccessObjectKind.Template)
                results.AddRange(QueryObjects(galaxy, EgObjectIsTemplateOrInstance.gObjectIsTemplate, "template", pattern));

            if (kind == GRAccessObjectKind.All || kind == GRAccessObjectKind.Instance)
                results.AddRange(QueryObjects(galaxy, EgObjectIsTemplateOrInstance.gObjectIsInstance, "instance", pattern));

            return results;
        }

        public static IReadOnlyList<GRAccessAttributeInfo> QueryAttributes(
            IGalaxy galaxy,
            GRAccessObjectKind kind,
            string objectName,
            bool configurableOnly)
        {
            if (galaxy == null) throw new ArgumentNullException(nameof(galaxy));
            if (string.IsNullOrWhiteSpace(objectName))
                throw new ArgumentException("Object name is required.", nameof(objectName));

            var obj = FindSingleObject(galaxy, kind, objectName);
            var attrs = configurableOnly ? obj.ConfigurableAttributes : obj.Attributes;

            var results = new List<GRAccessAttributeInfo>();
            if (attrs == null)
                return results;

            for (var i = 1; i <= attrs.count; i++)
            {
                var attr = attrs[i];
                if (attr == null)
                    continue;

                results.Add(new GRAccessAttributeInfo
                {
                    Name = TryRead(() => attr.Name),
                    DataType = TryRead(() => attr.DataType.ToString()),
                    Category = TryRead(() => attr.AttributeCategory.ToString()),
                    SecurityClassification = TryRead(() => attr.SecurityClassification.ToString()),
                    Locked = TryRead(() => attr.Locked.ToString()),
                    UpperBoundDim1 = TryReadNullableShort(() => attr.UpperBoundDim1),
                    HasBuffer = TryReadNullableBool(() => attr.HasBuffer),
                    RuntimeSetHandler = TryReadNullableBool(() => attr.RtSethandler),
                    ConfigSetHandler = TryReadNullableBool(() => attr.CfgSethandler)
                });
            }

            return results;
        }

        private static IEnumerable<GRAccessObjectInfo> QueryObjects(
            IGalaxy galaxy,
            EgObjectIsTemplateOrInstance templateOrInstance,
            string kind,
            string pattern)
        {
            var objects = galaxy.QueryObjects(
                templateOrInstance,
                EConditionType.namedLike,
                pattern,
                EMatch.MatchCondition);

            GRAccessDiagnostics.ThrowIfFailed(galaxy.CommandResult, "QueryObjects");

            if (objects == null)
                yield break;

            for (var i = 1; i <= objects.count; i++)
            {
                var obj = objects[i];
                if (obj == null)
                    continue;

                yield return new GRAccessObjectInfo
                {
                    Kind = kind,
                    Tagname = obj.Tagname,
                    ContainedName = obj.ContainedName,
                    HierarchicalName = obj.HierarchicalName,
                    CheckoutStatus = TryRead(() => obj.CheckoutStatus.ToString())
                };
            }
        }

        private static IgObject FindSingleObject(IGalaxy galaxy, GRAccessObjectKind kind, string objectName)
        {
            if (kind == GRAccessObjectKind.All)
            {
                var template = FindSingleObjectOrNull(galaxy, EgObjectIsTemplateOrInstance.gObjectIsTemplate, objectName);
                if (template != null)
                    return template;

                var instance = FindSingleObjectOrNull(galaxy, EgObjectIsTemplateOrInstance.gObjectIsInstance, objectName);
                if (instance != null)
                    return instance;
            }
            else
            {
                var templateOrInstance = kind == GRAccessObjectKind.Template
                    ? EgObjectIsTemplateOrInstance.gObjectIsTemplate
                    : EgObjectIsTemplateOrInstance.gObjectIsInstance;

                var obj = FindSingleObjectOrNull(galaxy, templateOrInstance, objectName);
                if (obj != null)
                    return obj;
            }

            throw new InvalidOperationException($"Object '{objectName}' was not found.");
        }

        private static IgObject FindSingleObjectOrNull(
            IGalaxy galaxy,
            EgObjectIsTemplateOrInstance templateOrInstance,
            string objectName)
        {
            var names = new[] { objectName };
            var objects = galaxy.QueryObjectsByName(templateOrInstance, ref names);
            GRAccessDiagnostics.ThrowIfFailed(galaxy.CommandResult, "QueryObjectsByName");

            if (objects == null || objects.count == 0)
                return null;

            return objects[1];
        }

        private static string TryRead(Func<string> read)
        {
            try
            {
                return read() ?? string.Empty;
            }
            catch
            {
                return string.Empty;
            }
        }

        private static bool? TryReadNullableBool(Func<bool> read)
        {
            try
            {
                return read();
            }
            catch
            {
                return null;
            }
        }

        private static short? TryReadNullableShort(Func<short> read)
        {
            try
            {
                return read();
            }
            catch
            {
                return null;
            }
        }
    }

    internal enum GRAccessObjectKind
    {
        All,
        Template,
        Instance
    }

    public sealed class GRAccessObjectInfo
    {
        public string Kind { get; set; } = string.Empty;
        public string Tagname { get; set; } = string.Empty;
        public string ContainedName { get; set; } = string.Empty;
        public string HierarchicalName { get; set; } = string.Empty;
        public string CheckoutStatus { get; set; } = string.Empty;
        public string DerivedFrom { get; set; } = string.Empty;
        public string BasedOn { get; set; } = string.Empty;
        public string Area { get; set; } = string.Empty;
        public string Host { get; set; } = string.Empty;
        public string Container { get; set; } = string.Empty;
        public string Toolset { get; set; } = string.Empty;
        public string SecurityGroup { get; set; } = string.Empty;
        public string ConfigVersion { get; set; } = string.Empty;
        public string DeploymentStatus { get; set; } = string.Empty;
        public string DeployedVersion { get; set; } = string.Empty;
        public string ValidationErrors { get; set; } = string.Empty;
        public string ValidationWarnings { get; set; } = string.Empty;
    }

    internal sealed class GRAccessAttributeInfo
    {
        public string Name { get; set; } = string.Empty;
        public string DataType { get; set; } = string.Empty;
        public string Category { get; set; } = string.Empty;
        public string SecurityClassification { get; set; } = string.Empty;
        public string Locked { get; set; } = string.Empty;
        public short? UpperBoundDim1 { get; set; }
        public bool? HasBuffer { get; set; }
        public bool? RuntimeSetHandler { get; set; }
        public bool? ConfigSetHandler { get; set; }
    }
}
