using EntityFirst.Module.BusinessObjects;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Entity.Core.Common.CommandTrees;
using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure.Interception;
using System.Linq;

// namespaces...
namespace EntityFirst.Module.Interceptors
{
    // public classes...
    public class TenantCommandTreeInterceptor : IDbCommandTreeInterceptor
    {
        // public methods...
        public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext)
        {
            if (interceptionContext.OriginalResult.DataSpace == DataSpace.SSpace)
            {
                var queryCommand = interceptionContext.Result as DbQueryCommandTree;
                if (queryCommand != null)
                {
                    var newQuery = queryCommand.Query.Accept(new TenantQueryVisitor());
                    interceptionContext.Result = new DbQueryCommandTree(
                        queryCommand.MetadataWorkspace,
                        queryCommand.DataSpace,
                        newQuery);
                    return;
                }
                var insertCommand = interceptionContext.Result as DbInsertCommandTree;
                if (insertCommand != null)
                {
                    var column = TenantAwareAttribute.GetTenantColumnName(insertCommand.Target.VariableType.EdmType);
                    if (!string.IsNullOrEmpty(column))
                    {
                        var variableReference = DbExpressionBuilder.Variable(insertCommand.Target.VariableType,
                            insertCommand.Target.VariableName);
                        var tenantProperty = DbExpressionBuilder.Property(variableReference, column);
                        var tenantSetClause =
                            DbExpressionBuilder.SetClause(tenantProperty, DbExpression.FromInt32(Global_Attributes.CompanyFilter));
                        var filteredSetClauses =
                            insertCommand.SetClauses.Cast<DbSetClause>()
                                .Where(sc => ((DbPropertyExpression)sc.Property).Property.Name != column);
                        var finalSetClauses =
                            new ReadOnlyCollection<DbModificationClause>
                            (new List<DbModificationClause>(filteredSetClauses)
                        {
                            tenantSetClause
                        });
                        var newInsertCommand = new DbInsertCommandTree(
                            insertCommand.MetadataWorkspace,
                            insertCommand.DataSpace,
                            insertCommand.Target,
                            finalSetClauses,
                            insertCommand.Returning);
                        interceptionContext.Result = newInsertCommand;
                        return;
                    }
                }
                var updateCommand = interceptionContext.Result as DbUpdateCommandTree;
                if (updateCommand != null)
                {
                    var column = TenantAwareAttribute.GetTenantColumnName(updateCommand.Target.VariableType.EdmType);
                    if (!string.IsNullOrEmpty(column))
                    {
                        var variableReference = DbExpressionBuilder.Variable(updateCommand.Target.VariableType,
                            updateCommand.Target.VariableName);
                        var tenantProperty = DbExpressionBuilder.Property(variableReference, column);
                        var tenantIdWherePredicate = DbExpressionBuilder.Equal(tenantProperty, DbExpression.FromInt32(Global_Attributes.CompanyFilter));
                        var filteredSetClauses =
                            updateCommand.SetClauses.Cast<DbSetClause>()
                                .Where(sc => ((DbPropertyExpression)sc.Property).Property.Name != column);
                        var finalSetClauses =
                            new ReadOnlyCollection<DbModificationClause>(new List<DbModificationClause>(filteredSetClauses));
                        var initialPredicate = updateCommand.Predicate;
                        var finalPredicate = initialPredicate.And(tenantIdWherePredicate);
                        var newUpdateCommand = new DbUpdateCommandTree(
                            updateCommand.MetadataWorkspace,
                            updateCommand.DataSpace,
                            updateCommand.Target,
                            finalPredicate,
                            finalSetClauses,
                            updateCommand.Returning);
                        interceptionContext.Result = newUpdateCommand;
                        return;
                    }
                }
                var deleteCommand = interceptionContext.Result as DbDeleteCommandTree;
                if (deleteCommand != null)
                {
                    var column = TenantAwareAttribute.GetTenantColumnName(deleteCommand.Target.VariableType.EdmType);
                    if (!string.IsNullOrEmpty(column))
                    {
                        var variableReference = DbExpressionBuilder.Variable(deleteCommand.Target.VariableType,
                            deleteCommand.Target.VariableName);
                        var tenantProperty = DbExpressionBuilder.Property(variableReference, column);
                        var tenantIdWherePredicate = DbExpressionBuilder.Equal(tenantProperty, DbExpression.FromInt32(Global_Attributes.CompanyFilter));
                        var initialPredicate = deleteCommand.Predicate;
                        var finalPredicate = initialPredicate.And(tenantIdWherePredicate);
                        var newDeleteCommand = new DbDeleteCommandTree(
                            deleteCommand.MetadataWorkspace,
                            deleteCommand.DataSpace,
                            deleteCommand.Target,
                            finalPredicate);
                        interceptionContext.Result = newDeleteCommand;
                        return;
                    }
                }
            }
        }
        // public classes...
        public class TenantQueryVisitor : DefaultExpressionVisitor
        {
            // public methods...
            public override DbExpression Visit(DbScanExpression expression)
            {
                var column = TenantAwareAttribute.GetTenantColumnName(expression.Target.ElementType);
                if (!string.IsNullOrEmpty(column))
                {
                    var dbExpression = base.Visit(expression);
                    var currentExpressionBinding = DbExpressionBuilder.Bind(dbExpression);
                    var variableReference = DbExpressionBuilder.Variable(currentExpressionBinding.VariableType,
                        currentExpressionBinding.VariableName);
                    var tenantProperty = DbExpressionBuilder.Property(variableReference, column);
                    var tenantParameter = DbExpressionBuilder.Parameter(tenantProperty.Property.TypeUsage,
                        TenantAwareAttribute.TenantIdFilterParameterName);
                    var filterExpression = DbExpressionBuilder.Equal(tenantProperty, tenantParameter);
                    return DbExpressionBuilder.Filter(currentExpressionBinding, filterExpression);
                }
                return base.Visit(expression);
            }
        }
    }
}
