<commit_before>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Signum.Utilities;
using Signum.Entities;
using Signum.Utilities.DataStructures;
using System.Reflection;
using Signum.Engine.Maps;
using System.Threading;
using Signum.Utilities.Reflection;
using Signum.Utilities.ExpressionTrees;
using Signum.Engine.Basics;
using Signum.Engine.DynamicQuery;
using Signum.Entities.Reflection;
using System.Linq.Expressions;
using Signum.Entities.Basics;
using System.Data.Common;
using Signum.Engine.Operations.Internal;
using static Signum.Engine.Maps.SchemaBuilder;

namespace Signum.Engine.Operations
{
    public static class OperationLogic
    {
        static Expression<Func<Entity, IQueryable<OperationLogEntity>>> OperationLogsEntityExpression =
            e => Database.Query<OperationLogEntity>().Where(a => a.Target.RefersTo(e));
        [ExpressionField]
        public static IQueryable<OperationLogEntity> OperationLogs(this Entity e)
        {
            return OperationLogsEntityExpression.Evaluate(e);
        }

        static Expression<Func<OperationSymbol, IQueryable<OperationLogEntity>>> LogsExpression =
            o => Database.Query<OperationLogEntity>().Where(a => a.Operation == o);
        [ExpressionField]
        public static IQueryable<OperationLogEntity> Logs(this OperationSymbol o)
        {
            return LogsExpression.Evaluate(o);
        }

        static Polymorphic<Dictionary<OperationSymbol, IOperation>> operations = new Polymorphic<Dictionary<OperationSymbol, IOperation>>(PolymorphicMerger.InheritDictionaryInterfaces, typeof(IEntity));

        static ResetLazy<Dictionary<OperationSymbol, List<Type>>> operationsFromKey = new ResetLazy<Dictionary<OperationSymbol, List<Type>>>(() =>
        {
            return (from t in operations.OverridenTypes
                    from d in operations.GetDefinition(t).Keys
                    group t by d into g
                    select KVP.Create(g.Key, g.ToList())).ToDictionary();
        });


        public static HashSet<OperationSymbol> RegisteredOperations
        {
            get { return operations.OverridenValues.SelectMany(a => a.Keys).ToHashSet(); }
        }
        
        static readonly Variable<ImmutableStack<Type>> allowedTypes = Statics.ThreadVariable<ImmutableStack<Type>>("saveOperationsAllowedTypes");

        public static bool AllowSaveGlobally { get; set; }

        public static bool IsSaveAllowedInContext(Type type)
        {
            var stack = allowedTypes.Value;
            return (stack != null && stack.Contains(type));
        }
        
        public static IDisposable AllowSave<T>() where T : class, IEntity
        {
            return AllowSave(typeof(T));
        }

        public static IDisposable AllowSave(Type type)
        {
            allowedTypes.Value = (allowedTypes.Value ?? ImmutableStack<Type>.Empty).Push(type);

            return new Disposable(() => allowedTypes.Value = allowedTypes.Value.Pop());
        }

        public static void AssertStarted(SchemaBuilder sb)
        {
            sb.AssertDefined(ReflectionTools.GetMethodInfo(() => Start(null, null)));
        }

        public static void Start(SchemaBuilder sb, DynamicQueryManager dqm)
        {
            if (sb.NotDefined(MethodInfo.GetCurrentMethod()))
            {
                sb.Include<OperationLogEntity>()
                    .WithQuery(dqm, lo => new
                    {
                        Entity = lo,
                        lo.Id,
                        lo.Target,
                        lo.Operation,
                        lo.User,
                        lo.Start,
                        lo.End,
                        lo.Exception
                    });

                SymbolLogic<OperationSymbol>.Start(sb, dqm, () => RegisteredOperations);

                sb.Include<OperationSymbol>()
                    .WithQuery(dqm, os => new
                    {
                        Entity = os,
                        os.Id,
                        os.Key
                    });

                dqm.RegisterExpression((OperationSymbol o) => o.Logs(), () => OperationMessage.Logs.NiceToString());
                dqm.RegisterExpression((Entity o) => o.OperationLogs(), () => typeof(OperationLogEntity).NicePluralName());

                sb.Schema.EntityEventsGlobal.Saving += EntityEventsGlobal_Saving;

                sb.Schema.Table<OperationSymbol>().PreDeleteSqlSync += new Func<Entity, SqlPreCommand>(Operation_PreDeleteSqlSync);
                sb.Schema.Table<TypeEntity>().PreDeleteSqlSync += new Func<Entity, SqlPreCommand>(Type_PreDeleteSqlSync);

                sb.Schema.SchemaCompleted += OperationLogic_Initializing;

                ExceptionLogic.DeleteLogs += ExceptionLogic_DeleteLogs;
            }
        }

        public static void ExceptionLogic_DeleteLogs(DeleteLogParametersEntity parameters)
        {
            Database.Query<OperationLogEntity>().Where(o => o.Start < parameters.DateLimit).UnsafeDeleteChunks(parameters.ChunkSize, parameters.MaxChunks);
        }

        static void OperationLogic_Initializing()
        {
            var types = Schema.Current.Tables.Keys
                .Where(t => EntityKindCache.GetAttribute(t) == null)
                .Select(a => "'" + a.TypeName() + "'")
                .CommaAnd();

            if (types.HasText())
                throw new InvalidOperationException($"{0} has not EntityTypeAttribute".FormatWith(types));

            var errors = (from t in Schema.Current.Tables.Keys
                          let attr = EntityKindCache.GetAttribute(t)
                          where attr.RequiresSaveOperation && !HasExecuteNoLite(t)
                          select attr.IsRequiresSaveOperationOverriden ?
                            "'{0}' has '{1}' set to true, but no operation for saving has been implemented.".FormatWith(t.TypeName(), nameof(attr.RequiresSaveOperation)) :
                            "'{0}' is 'EntityKind.{1}', but no operation for saving has been implemented.".FormatWith(t.TypeName(), attr.EntityKind)).ToList();

            if (errors.Any())
                throw new InvalidOperationException(errors.ToString("\r\n") +  @"
Consider the following options:
    * Implement an operation for saving using 'save' snippet.
    * Change the EntityKind to a more appropiated one. 
    * Exceptionally, override the property EntityTypeAttribute.RequiresSaveOperation for your particular entity.");
        }

        static SqlPreCommand Operation_PreDeleteSqlSync(Entity arg)
        {
            var t = Schema.Current.Table<OperationLogEntity>();
            var f = (FieldReference)Schema.Current.Field((OperationLogEntity ol) => ol.Operation);

            var param = Connector.Current.ParameterBuilder.CreateReferenceParameter("@id", arg.Id, t.PrimaryKey);

            return new SqlPreCommandSimple("DELETE FROM {0} WHERE {1} = {2}".FormatWith(t.Name, f.Name, param.ParameterName), new List<DbParameter> { param });
        }

        static SqlPreCommand Type_PreDeleteSqlSync(Entity arg)
        {
            var t = Schema.Current.Table<OperationLogEntity>();
            var f = ((FieldImplementedByAll)Schema.Current.Field((OperationLogEntity ol) => ol.Target)).ColumnType;

            var param = Connector.Current.ParameterBuilder.CreateReferenceParameter("@id", arg.Id, t.PrimaryKey);<commit_msg>avoid throw exception on start in OperationLogic_Initializing<commit_after>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Signum.Utilities;
using Signum.Entities;
using Signum.Utilities.DataStructures;
using System.Reflection;
using Signum.Engine.Maps;
using System.Threading;
using Signum.Utilities.Reflection;
using Signum.Utilities.ExpressionTrees;
using Signum.Engine.Basics;
using Signum.Engine.DynamicQuery;
using Signum.Entities.Reflection;
using System.Linq.Expressions;
using Signum.Entities.Basics;
using System.Data.Common;
using Signum.Engine.Operations.Internal;
using static Signum.Engine.Maps.SchemaBuilder;

namespace Signum.Engine.Operations
{
    public static class OperationLogic
    {
        static Expression<Func<Entity, IQueryable<OperationLogEntity>>> OperationLogsEntityExpression =
            e => Database.Query<OperationLogEntity>().Where(a => a.Target.RefersTo(e));
        [ExpressionField]
        public static IQueryable<OperationLogEntity> OperationLogs(this Entity e)
        {
            return OperationLogsEntityExpression.Evaluate(e);
        }

        static Expression<Func<OperationSymbol, IQueryable<OperationLogEntity>>> LogsExpression =
            o => Database.Query<OperationLogEntity>().Where(a => a.Operation == o);
        [ExpressionField]
        public static IQueryable<OperationLogEntity> Logs(this OperationSymbol o)
        {
            return LogsExpression.Evaluate(o);
        }

        static Polymorphic<Dictionary<OperationSymbol, IOperation>> operations = new Polymorphic<Dictionary<OperationSymbol, IOperation>>(PolymorphicMerger.InheritDictionaryInterfaces, typeof(IEntity));

        static ResetLazy<Dictionary<OperationSymbol, List<Type>>> operationsFromKey = new ResetLazy<Dictionary<OperationSymbol, List<Type>>>(() =>
        {
            return (from t in operations.OverridenTypes
                    from d in operations.GetDefinition(t).Keys
                    group t by d into g
                    select KVP.Create(g.Key, g.ToList())).ToDictionary();
        });


        public static HashSet<OperationSymbol> RegisteredOperations
        {
            get { return operations.OverridenValues.SelectMany(a => a.Keys).ToHashSet(); }
        }
        
        static readonly Variable<ImmutableStack<Type>> allowedTypes = Statics.ThreadVariable<ImmutableStack<Type>>("saveOperationsAllowedTypes");

        public static bool AllowSaveGlobally { get; set; }

        public static bool IsSaveAllowedInContext(Type type)
        {
            var stack = allowedTypes.Value;
            return (stack != null && stack.Contains(type));
        }
        
        public static IDisposable AllowSave<T>() where T : class, IEntity
        {
            return AllowSave(typeof(T));
        }

        public static IDisposable AllowSave(Type type)
        {
            allowedTypes.Value = (allowedTypes.Value ?? ImmutableStack<Type>.Empty).Push(type);

            return new Disposable(() => allowedTypes.Value = allowedTypes.Value.Pop());
        }

        public static void AssertStarted(SchemaBuilder sb)
        {
            sb.AssertDefined(ReflectionTools.GetMethodInfo(() => Start(null, null)));
        }

        public static void Start(SchemaBuilder sb, DynamicQueryManager dqm)
        {
            if (sb.NotDefined(MethodInfo.GetCurrentMethod()))
            {
                sb.Include<OperationLogEntity>()
                    .WithQuery(dqm, lo => new
                    {
                        Entity = lo,
                        lo.Id,
                        lo.Target,
                        lo.Operation,
                        lo.User,
                        lo.Start,
                        lo.End,
                        lo.Exception
                    });

                SymbolLogic<OperationSymbol>.Start(sb, dqm, () => RegisteredOperations);

                sb.Include<OperationSymbol>()
                    .WithQuery(dqm, os => new
                    {
                        Entity = os,
                        os.Id,
                        os.Key
                    });

                dqm.RegisterExpression((OperationSymbol o) => o.Logs(), () => OperationMessage.Logs.NiceToString());
                dqm.RegisterExpression((Entity o) => o.OperationLogs(), () => typeof(OperationLogEntity).NicePluralName());

                sb.Schema.EntityEventsGlobal.Saving += EntityEventsGlobal_Saving;

                sb.Schema.Table<OperationSymbol>().PreDeleteSqlSync += new Func<Entity, SqlPreCommand>(Operation_PreDeleteSqlSync);
                sb.Schema.Table<TypeEntity>().PreDeleteSqlSync += new Func<Entity, SqlPreCommand>(Type_PreDeleteSqlSync);

                sb.Schema.SchemaCompleted += OperationLogic_Initializing;

                ExceptionLogic.DeleteLogs += ExceptionLogic_DeleteLogs;
            }
        }

        public static void ExceptionLogic_DeleteLogs(DeleteLogParametersEntity parameters)
        {
            Database.Query<OperationLogEntity>().Where(o => o.Start < parameters.DateLimit).UnsafeDeleteChunks(parameters.ChunkSize, parameters.MaxChunks);
        }

        static void OperationLogic_Initializing()
        {
            try
            {
                var types = Schema.Current.Tables.Keys
                    .Where(t => EntityKindCache.GetAttribute(t) == null)
                    .Select(a => "'" + a.TypeName() + "'")
                    .CommaAnd();

                if (types.HasText())
                    throw new InvalidOperationException($"{0} has not EntityTypeAttribute".FormatWith(types));

                var errors = (from t in Schema.Current.Tables.Keys
                              let attr = EntityKindCache.GetAttribute(t)
                              where attr.RequiresSaveOperation && !HasExecuteNoLite(t)
                              select attr.IsRequiresSaveOperationOverriden ?
                                "'{0}' has '{1}' set to true, but no operation for saving has been implemented.".FormatWith(t.TypeName(), nameof(attr.RequiresSaveOperation)) :
                                "'{0}' is 'EntityKind.{1}', but no operation for saving has been implemented.".FormatWith(t.TypeName(), attr.EntityKind)).ToList();

                if (errors.Any())
                    throw new InvalidOperationException(errors.ToString("\r\n") + @"
Consider the following options:
    * Implement an operation for saving using 'save' snippet.
    * Change the EntityKind to a more appropiated one. 
    * Exceptionally, override the property EntityTypeAttribute.RequiresSaveOperation for your particular entity.");
            }
            catch
            {
                //Just to notify the developer
            }
        }

        static SqlPreCommand Operation_PreDeleteSqlSync(Entity arg)
        {
            var t = Schema.Current.Table<OperationLogEntity>();
            var f = (FieldReference)Schema.Current.Field((OperationLogEntity ol) => ol.Operation);

            var param = Connector.Current.ParameterBuilder.CreateReferenceParameter("@id", arg.Id, t.PrimaryKey);

            return new SqlPreCommandSimple("DELETE FROM {0} WHERE {1} = {2}".FormatWith(t.Name, f.Name, param.ParameterName), new List<DbParameter> { param });
        }

        static SqlPreCommand Type_PreDeleteSqlSync(Entity arg)
        {
            var t = Schema.Current.Table<OperationLogEntity>();
            var f = ((FieldImplementedByAll)Schema.Current.Field((OperationLogEntity ol) => ol.Target)).ColumnType;

            var param = Connector.Current.ParameterBuilder.CreateReferenceParameter("@id", arg.Id, t.PrimaryKey);
{'fraction_non_alphanumeric': 0.09966953073364176, 'fraction_numerical': 0.0010575016523463319, 'mean_word_length': 2.2772363006281133, 'pattern_counts': {'":': 0, '<': 81, '<?xml version=': 0, '>': 119, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4880503', 'n_tokens_mistral': 3857, 'n_tokens_neox': 3722, 'n_words': 774}