// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using Microsoft.Internal.Collections;

namespace System.ComponentModel.Composition.Hosting
{
    public partial class ImportEngine
    {
        /// <summary>
        ///     Used by the <see cref="ImportEngine"/> to manage the composition of a given part.
        ///     It stores things like the list of disposable exports used to satisfy the imports as
        ///     well as the caching of the exports discovered during previewing of a part.
        /// </summary>
        private sealed class PartManager
        {
            private Dictionary<ImportDefinition, List<IDisposable>>? _importedDisposableExports;
            private Dictionary<ImportDefinition, Export[]?>? _importCache;
            private string[]? _importedContractNames;
            private readonly ComposablePart _part;
            private ImportState _state = ImportState.NoImportsSatisfied;
            private readonly ImportEngine _importEngine;

            public PartManager(ImportEngine importEngine, ComposablePart part)
            {
                _importEngine = importEngine;
                _part = part;
            }

            public ComposablePart Part
            {
                get
                {
                    return _part;
                }
            }

            public ImportState State
            {
                get
                {
                    using (_importEngine._lock.LockStateForRead())
                    {
                        return _state;
                    }
                }
                set
                {
                    using (_importEngine._lock.LockStateForWrite())
                    {
                        _state = value;
                    }
                }
            }

            public bool TrackingImports { get; set; }

            public IEnumerable<string> GetImportedContractNames()
            {
                if (Part == null)
                {
                    return Enumerable.Empty<string>();
                }

                return _importedContractNames ??= Part.ImportDefinitions.Select(import => import.ContractName ?? ImportDefinition.EmptyContractName).Distinct().ToArray();
            }

            public CompositionResult TrySetImport(ImportDefinition import, Export[] exports)
            {
                try
                {
                    Part.SetImport(import, exports);
                    UpdateDisposableDependencies(import, exports);
                    return CompositionResult.SucceededResult;
                }
                catch (CompositionException ex)
                {   // Pulling on one of the exports failed

                    return new CompositionResult(
                        ErrorBuilder.CreatePartCannotSetImport(Part, import, ex));
                }
                catch (ComposablePartException ex)
                {   // Type mismatch between export and import

                    return new CompositionResult(
                        ErrorBuilder.CreatePartCannotSetImport(Part, import, ex));
                }
            }

            public void SetSavedImport(ImportDefinition import, Export[]? exports, AtomicComposition? atomicComposition)
            {
                if (atomicComposition != null)
                {
                    var savedExports = GetSavedImport(import);

                    // Add a revert action to revert the stored exports
                    // in the case that this atomicComposition gets rolled back.
                    atomicComposition.AddRevertAction(() =>
                        SetSavedImport(import, savedExports, null));
                }

                _importCache ??= new Dictionary<ImportDefinition, Export[]?>();
                _importCache[import] = exports;
            }

            public Export[]? GetSavedImport(ImportDefinition import)
            {
                Export[]? exports = null;
                // We don't care about the return value we just want the exports
                // and if it isn't present we just return the initialized null value
                _importCache?.TryGetValue(import, out exports);
                return exports;
            }

            public void ClearSavedImports()
            {
                _importCache = null;
            }

            public CompositionResult TryOnComposed()
            {
                try
                {
                    Part.Activate();
                    return CompositionResult.SucceededResult;
                }
                catch (ComposablePartException ex)
                {   // Type failed to be constructed, imports could not be set, etc
                    return new CompositionResult(
                        ErrorBuilder.CreatePartCannotActivate(Part, ex));
                }
            }

            public void UpdateDisposableDependencies(ImportDefinition import, Export[] exports)
            {
                // Determine if there are any new disposable exports, optimizing for the most
                // likely case, which is that there aren't any
                List<IDisposable>? disposableExports = null;
                foreach (var export in exports)
                {
                    if (export is IDisposable disposableExport)
                    {
                        disposableExports ??= new List<IDisposable>();
                        disposableExports.Add(disposableExport);
                    }
                }

                // Dispose any existing references previously set on this import
                List<IDisposable>? oldDisposableExports = null;
                if (_importedDisposableExports != null &&
                    _importedDisposableExports.TryGetValue(import, out oldDisposableExports))
                {
                    oldDisposableExports.ForEach(disposable => disposable.Dispose());

                    // If there aren't any replacements, get rid of the old storage
                    if (disposableExports == null)
                    {
                        _importedDisposableExports.Remove(import);
                        if (_importedDisposableExports.Count == 0)
                        {
                            _importedDisposableExports = null;
                        }

                        return;
                    }
                }

                // Record the new collection
                if (disposableExports != null)
                {
                    _importedDisposableExports ??= new Dictionary<ImportDefinition, List<IDisposable>>();
                    _importedDisposableExports[import] = disposableExports;
                }
            }

            public void DisposeAllDependencies()
            {
                if (_importedDisposableExports != null)
                {
                    IEnumerable<IDisposable> dependencies = _importedDisposableExports.Values
                        .SelectMany(exports => exports);

                    _importedDisposableExports = null;

                    dependencies.ForEach(disposableExport => disposableExport.Dispose());
                }
            }
        }
    }
}
