using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Reflection;
using DevExpress.Data.Filtering.Helpers;
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.ConditionalAppearance;
using DevExpress.ExpressApp.Editors;
using DevExpress.ExpressApp.Model;
using Fasterflect;
using Xpand.Persistent.Base.General;
using Xpand.Persistent.Base.ModelAdapter;
using Xpand.Utils.Fastreflect;

namespace Xpand.ExpressApp.SystemModule.Appearance {
    public class AppearanceController : ViewController, IModelExtender {
        

        protected override void OnDeactivated() {
            base.OnDeactivated();
            Frame.GetController<DevExpress.ExpressApp.ConditionalAppearance.AppearanceController>(controller => {
                controller.CustomApplyAppearance -= AppearanceControllerOnCustomApplyAppearance;
                controller.CustomCreateAppearanceRule -= AppearanceControllerOnCustomCreateAppearanceRule;
                controller.CustomGetIsRulePropertiesEmpty -= AppearanceControllerOnCustomGetIsRulePropertiesEmpty;
            });
        }

        protected override void OnActivated() {
            base.OnActivated();
            Frame.GetController<DevExpress.ExpressApp.ConditionalAppearance.AppearanceController>(controller => {
                controller.CustomApplyAppearance += AppearanceControllerOnCustomApplyAppearance;
                controller.CustomCreateAppearanceRule += AppearanceControllerOnCustomCreateAppearanceRule;
                controller.CustomGetIsRulePropertiesEmpty += AppearanceControllerOnCustomGetIsRulePropertiesEmpty;
            });
            
        }

        private void AppearanceControllerOnCustomApplyAppearance(object sender, ApplyAppearanceEventArgs e) {
            var appearanceRules =(List<DevExpress.ExpressApp.ConditionalAppearance.AppearanceRule>)
                ((DevExpress.ExpressApp.ConditionalAppearance.AppearanceController) sender).CallMethod("GetAppearanceRules",new[]{typeof (IViewInfo), typeof (string), typeof (string), typeof (object)}, 
                     e.ViewInfo, e.ItemType, e.ItemName, e.Item);
            foreach (var result in appearanceRules.OfType<AppearanceRule>()) {
                var conditionalAppearanceItems = result.Validate(e.ContextObjects, e.EvaluatorContextDescriptor);
                e.AppearanceObject.Items.AddRange(conditionalAppearanceItems);
            }
        }

        private void AppearanceControllerOnCustomGetIsRulePropertiesEmpty(object sender, CustomGetIsRulePropertiesEmptyEventArgs e){
            var appearanceRuleProperties = e.RuleProperties as CachedAppearanceRuleProperties;
            if (appearanceRuleProperties != null)
                e.IsEmpty = !appearanceRuleProperties.Properties.HasValue(typeof(IModelAppearanceFont));
        }

        private void AppearanceControllerOnCustomCreateAppearanceRule(object sender, CustomCreateAppearanceRuleEventArgs e){
            var propertyInfo = e.RuleProperties.GetType().PropertyX("Attribute");
            if (propertyInfo!=null&& e.RuleProperties.GetPropertyValue("Attribute") == null) {
                var xpandAppearanceRuleProperties = e.RuleProperties as IXpandAppearanceRuleProperties;
                if (xpandAppearanceRuleProperties != null)
                    e.Rule = new AppearanceRule(new CachedAppearanceRuleProperties(xpandAppearanceRuleProperties), View.ObjectSpace);
            }
        }

        public void ExtendModelInterfaces(ModelInterfaceExtenders extenders) {
            extenders.Add<IAppearance, IXpandAppearanceRuleProperties>();
        }
    }

    public class AppearanceRule : DevExpress.ExpressApp.ConditionalAppearance.AppearanceRule {
        public AppearanceRule(CachedAppearanceRuleProperties properties, IObjectSpace objectSpace)
            : base(properties, objectSpace) {
            Properties = properties;
        }

        public new CachedAppearanceRuleProperties Properties{ get; }

        internal IList<IConditionalAppearanceItem> Validate(object[] contextObjects, EvaluatorContextDescriptor evaluatorContextDescriptor) {
            var ruleValid = (bool)this.CallMethod("GetRuleValid", new[] { typeof(object[]), typeof(EvaluatorContextDescriptor) }, contextObjects, evaluatorContextDescriptor);
            var result = new List<IConditionalAppearanceItem>();
            AppearanceState state = ruleValid ? AppearanceState.CustomValue : AppearanceState.ResetValue;
            if (Properties.Properties.HasValue(typeof(IModelAppearanceFont))) {
                result.Add(new AppearanceItemFont(state, Properties.Priority, Properties.Properties));
            }
            return result;
        }

    }

    public class AppearanceItemFont : AppearanceItemBase {
        private IModelAppearanceFont _modelAppearanceFont;
        public AppearanceItemFont(AppearanceState state, int priority, IModelAppearanceFont modelAppearanceFont)
            : base(state, priority) {
            if (state == AppearanceState.CustomValue) {
                _modelAppearanceFont = modelAppearanceFont;
            }
        }
        public IModelAppearanceFont ModelAppearanceFont {
            get { return _modelAppearanceFont; }
            set {
                State = value != null ? AppearanceState.CustomValue : AppearanceState.ResetValue;
                _modelAppearanceFont = value;
            }
        }
        public override bool IsCombineValue => true;

        protected override void ApplyCore(object targetItem) {
            var appearanceFormat = targetItem as IAppearanceFormat;
            if (appearanceFormat != null && State != AppearanceState.None) {
                if (State == AppearanceState.CustomValue) {
                    var propertyNames = new[] { "Control.Font", "AppearanceObject.Font" };
                    foreach (var propertyName in propertyNames) {
                        var infoAndValue = GetPropertyInfoAndValue(appearanceFormat, propertyName);
                        if (infoAndValue != null && infoAndValue.Item1 != null) {
                            var fontBuilder = new FontBuilder(ModelAppearanceFont, infoAndValue.Item3);
                            var font = fontBuilder.GetFont();
                            infoAndValue.Item1.Set(infoAndValue.Item2, font);
                            break;
                        }
                    }
                }
            }
        }

        private Tuple<PropertyInfo, object, Font> GetPropertyInfoAndValue(IAppearanceFormat appearanceFormat, string propertyName) {
            var type = appearanceFormat.GetType();
            object obj = appearanceFormat;
            while (propertyName.Contains(".")) {
                var name = propertyName.Substring(0, propertyName.IndexOf(".", StringComparison.Ordinal));
                PropertyInfo propertyInfo;
                try{
                    propertyInfo = type.PropertyX(name, Flags.ExcludeHiddenMembers|Flags.InstancePublic);
                }
                catch (AmbiguousMatchException e){
                    throw new AmbiguousMatchException(type.FullName+", "+propertyName,e);
                }
                if (propertyInfo == null)
                    return null;
                propertyName = propertyName.Substring(propertyName.IndexOf(".", StringComparison.Ordinal) + 1);
                type = propertyInfo.PropertyType;
                obj = propertyInfo.Get(obj);
            }
            var property = type.Property(propertyName);
            return obj != null? (property != null? new Tuple<PropertyInfo, object, Font>(property, obj, (Font) property.Get(obj)): null): null;
        }
    }

    public class CachedAppearanceRuleProperties : DevExpress.ExpressApp.ConditionalAppearance.CachedAppearanceRuleProperties {
        public CachedAppearanceRuleProperties(IXpandAppearanceRuleProperties properties)
            : base(properties) {
            FontName = properties.FontName;
            Size = Size;
            Properties = properties;
        }

        public IXpandAppearanceRuleProperties Properties{ get; }

        public string FontName { get; set; }

        public float? Size { get; set; }
    }

    public interface IXpandAppearanceRuleProperties : IModelAppearanceFont, IAppearanceRuleProperties {
    }
}
