// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Immutable;
using System.Threading;
using Analyzer.Utilities;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.PointsToAnalysis;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.ValueContentAnalysis;

namespace Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.GlobalFlowStateAnalysis
{
    using GlobalFlowStateAnalysisDomain = MapAbstractDomain<AnalysisEntity, GlobalFlowStateAnalysisValueSet>;
    using GlobalFlowStateAnalysisData = DictionaryAnalysisData<AnalysisEntity, GlobalFlowStateAnalysisValueSet>;
    using GlobalFlowStateAnalysisResult = DataFlowAnalysisResult<GlobalFlowStateBlockAnalysisResult, GlobalFlowStateAnalysisValueSet>;
    using ValueContentAnalysisResult = DataFlowAnalysisResult<ValueContentBlockAnalysisResult, ValueContentAbstractValue>;

    /// <summary>
    /// Dataflow analysis to track set of global <see cref="IAbstractAnalysisValue"/>s enabled on each control flow path at each <see cref="IOperation"/> in the <see cref="ControlFlowGraph"/>.
    /// </summary>
    internal partial class GlobalFlowStateAnalysis : ForwardDataFlowAnalysis<GlobalFlowStateAnalysisData, GlobalFlowStateAnalysisContext, GlobalFlowStateAnalysisResult, GlobalFlowStateBlockAnalysisResult, GlobalFlowStateAnalysisValueSet>
    {
        internal static readonly GlobalFlowStateAnalysisDomain GlobalFlowStateAnalysisDomainInstance = new GlobalFlowStateAnalysisDomain(GlobalFlowStateAnalysisValueSetDomain.Instance);

        private GlobalFlowStateAnalysis(GlobalFlowStateAnalysisDomain analysisDomain, GlobalFlowStateDataFlowOperationVisitor operationVisitor)
            : base(analysisDomain, operationVisitor)
        {
        }

        /// <summary>
        /// Performs global flow state analysis and returns the analysis result.
        /// </summary>
        /// <param name="cfg">Control flow graph to analyze.</param>
        /// <param name="owningSymbol">Owning symbol for the analyzed <paramref name="cfg"/>.</param>
        /// <param name="createOperationVisitor">Delegate to create a <see cref="GlobalFlowStateDataFlowOperationVisitor"/> that performs the core analysis.</param>
        /// <param name="wellKnownTypeProvider">Well-known type provider for the compilation.</param>
        /// <param name="analyzerOptions">Analyzer options for analysis</param>
        /// <param name="rule"><see cref="DiagnosticDescriptor"/> for fetching any rule specific analyzer option values from <paramref name="analyzerOptions"/>.</param>
        /// <param name="performValueContentAnalysis">Flag to indicate if <see cref="ValueContentAnalysis.ValueContentAnalysis"/> should be performed.</param>
        /// <param name="pessimisticAnalysis">
        /// This boolean field determines if we should perform an optimistic OR a pessimistic analysis.
        /// For example, invoking a lambda method for which we do not know the target method being invoked can change/invalidate the current global flow state.
        /// An optimistic points to analysis assumes that the global flow state doesn't change for such scenarios.
        /// A pessimistic points to analysis resets the the global flow state to an unknown state for such scenarios.
        /// </param>
        /// <param name="cancellationToken">Token to cancel analysis.</param>
        /// <param name="valueContentAnalysisResult">Optional value content analysis result, if <paramref name="performValueContentAnalysis"/> is true</param>
        /// <param name="interproceduralAnalysisKind"><see cref="InterproceduralAnalysisKind"/> for the analysis.</param>
        /// <param name="interproceduralAnalysisPredicate">Optional predicate for interprocedural analysis.</param>
        /// <param name="additionalSupportedValueTypes">Additional value types for which the caller wants to track stored values during value content analysis.</param>
        /// <param name="getValueContentValueForAdditionalSupportedValueTypeOperation">
        /// Optional delegate to compute values for <paramref name="additionalSupportedValueTypes"/>.
        /// Must be non-null if <paramref name="additionalSupportedValueTypes"/> is non-empty.
        /// </param>
        /// <returns>Global flow state analysis result, or null if analysis did not succeed.</returns>
        public static GlobalFlowStateAnalysisResult? TryGetOrComputeResult(
            ControlFlowGraph cfg,
            ISymbol owningSymbol,
            Func<GlobalFlowStateAnalysisContext, GlobalFlowStateDataFlowOperationVisitor> createOperationVisitor,
            WellKnownTypeProvider wellKnownTypeProvider,
            AnalyzerOptions analyzerOptions,
            DiagnosticDescriptor rule,
            bool performValueContentAnalysis,
            bool pessimisticAnalysis,
            CancellationToken cancellationToken,
            out ValueContentAnalysisResult? valueContentAnalysisResult,
            InterproceduralAnalysisKind interproceduralAnalysisKind = InterproceduralAnalysisKind.None,
            InterproceduralAnalysisPredicate? interproceduralAnalysisPredicate = null,
            ImmutableArray<INamedTypeSymbol> additionalSupportedValueTypes = default,
            Func<IOperation, ValueContentAbstractValue>? getValueContentValueForAdditionalSupportedValueTypeOperation = null)
        {
            if (cfg == null)
            {
                throw new ArgumentNullException(nameof(cfg));
            }

            var interproceduralAnalysisConfig = InterproceduralAnalysisConfiguration.Create(
                analyzerOptions, rule, cfg, wellKnownTypeProvider.Compilation, interproceduralAnalysisKind, cancellationToken);
            var pointsToAnalysisKind = analyzerOptions.GetPointsToAnalysisKindOption(rule, owningSymbol, wellKnownTypeProvider.Compilation,
                defaultValue: PointsToAnalysisKind.PartialWithoutTrackingFieldsAndProperties, cancellationToken);
            return TryGetOrComputeResult(cfg, owningSymbol, createOperationVisitor, wellKnownTypeProvider, analyzerOptions,
                interproceduralAnalysisConfig, interproceduralAnalysisPredicate, pointsToAnalysisKind, pessimisticAnalysis,
                performValueContentAnalysis, out valueContentAnalysisResult,
                additionalSupportedValueTypes, getValueContentValueForAdditionalSupportedValueTypeOperation);
        }

        private static GlobalFlowStateAnalysisResult? TryGetOrComputeResult(
            ControlFlowGraph cfg,
            ISymbol owningSymbol,
            Func<GlobalFlowStateAnalysisContext, GlobalFlowStateDataFlowOperationVisitor> createOperationVisitor,
            WellKnownTypeProvider wellKnownTypeProvider,
            AnalyzerOptions analyzerOptions,
            InterproceduralAnalysisConfiguration interproceduralAnalysisConfig,
            InterproceduralAnalysisPredicate? interproceduralAnalysisPredicate,
            PointsToAnalysisKind pointsToAnalysisKind,
            bool pessimisticAnalysis,
            bool performValueContentAnalysis,
            out ValueContentAnalysisResult? valueContentAnalysisResult,
            ImmutableArray<INamedTypeSymbol> additionalSupportedValueTypes = default,
            Func<IOperation, ValueContentAbstractValue>? getValueContentValueForAdditionalSupportedValueTypeOperation = null)
        {
            RoslynDebug.Assert(owningSymbol != null);

            PointsToAnalysisResult? pointsToAnalysisResult = null;
            valueContentAnalysisResult = performValueContentAnalysis ?
                ValueContentAnalysis.ValueContentAnalysis.TryGetOrComputeResult(
                    cfg, owningSymbol, analyzerOptions, wellKnownTypeProvider,
                    pointsToAnalysisKind, interproceduralAnalysisConfig, out _,
                    out pointsToAnalysisResult, pessimisticAnalysis,
                    performCopyAnalysis: false, interproceduralAnalysisPredicate,
                    additionalSupportedValueTypes, getValueContentValueForAdditionalSupportedValueTypeOperation) :
                null;

            pointsToAnalysisResult ??= PointsToAnalysis.PointsToAnalysis.TryGetOrComputeResult(
                cfg, owningSymbol, analyzerOptions, wellKnownTypeProvider,
                pointsToAnalysisKind, interproceduralAnalysisConfig, interproceduralAnalysisPredicate);

            var analysisContext = GlobalFlowStateAnalysisContext.Create(
                GlobalFlowStateAnalysisValueSetDomain.Instance, wellKnownTypeProvider, cfg, owningSymbol,
                analyzerOptions, interproceduralAnalysisConfig, pessimisticAnalysis, pointsToAnalysisResult,
                valueContentAnalysisResult, c => TryGetOrComputeResultForAnalysisContext(c, createOperationVisitor), interproceduralAnalysisPredicate);
            return TryGetOrComputeResultForAnalysisContext(analysisContext, createOperationVisitor);
        }

        private static GlobalFlowStateAnalysisResult? TryGetOrComputeResultForAnalysisContext(
            GlobalFlowStateAnalysisContext analysisContext,
            Func<GlobalFlowStateAnalysisContext, GlobalFlowStateDataFlowOperationVisitor> createOperationVisitor)
        {
            var operationVisitor = createOperationVisitor(analysisContext);
            var analysis = new GlobalFlowStateAnalysis(GlobalFlowStateAnalysisDomainInstance, operationVisitor);
            return analysis.TryGetOrComputeResultCore(analysisContext, cacheResult: false);
        }

        protected override GlobalFlowStateAnalysisResult ToResult(GlobalFlowStateAnalysisContext analysisContext, GlobalFlowStateAnalysisResult dataFlowAnalysisResult)
        {
            // Update the operation state map with global values map
            // These are the values that analyzers care about.
            var operationVisitor = (GlobalFlowStateDataFlowOperationVisitor)OperationVisitor;
            return dataFlowAnalysisResult.With(operationVisitor.GetGlobalValuesMap());
        }

        protected override GlobalFlowStateBlockAnalysisResult ToBlockResult(BasicBlock basicBlock, GlobalFlowStateAnalysisData data)
            => new GlobalFlowStateBlockAnalysisResult(basicBlock, data);
    }
}
