﻿using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
using System.Security;
using TeamElem.Data.Serialization;

namespace TeamElem
{
    /// <summary>
    /// 弱参照を表します。
    /// </summary>
    /// <typeparam name="T">要素の型</typeparam>
    [Serializable]
    public struct Weak<T> : ISerializable where T : class
    {
        private readonly WeakReference target;

        public Weak(T value)
        {
            if (value != null)
                target = new WeakReference(value);
            else
                target = null;
        }
        public Weak(T value, bool trackResurrection)
        {
            if (value != null)
                target = new WeakReference(value, trackResurrection);
            else
                target = null;
        }
        [SecurityCritical]
        [SuppressMessage("Microsoft.Security", "CA2108:ReviewDeclarativeSecurityOnValueTypes")]
        private Weak(SerializationInfo info, StreamingContext context) : this(info.GetValue<T>(nameof(Value)), info.GetBoolean(nameof(TrackResurrection))) { }

        /// <summary>
        /// 現在の <see cref="Weak{T}"/> オブジェクトが参照するオブジェクトが、ガベージ コレクションで収集されているかどうかを示す値を取得します。
        /// </summary>
        public bool IsAlive => target?.IsAlive ?? false;
        /// <summary>
        /// 現在の <see cref="Weak{T}"/> オブジェクトが参照するオブジェクトが、ガベージ コレクションで収集されているかどうかを示す値を取得します。
        /// </summary>
        public bool IsLost => target != null && !target.IsAlive;
        /// <summary>
        /// 現在の <see cref="Weak{T}"/> オブジェクトが参照するオブジェクトを、その終了後に追跡するかどうかを示す値を取得します。
        /// </summary>
        public bool TrackResurrection => target?.TrackResurrection ?? false;

        /// <summary>
        /// 現在の System.WeakReference オブジェクトが参照するオブジェクトを取得します。
        /// </summary>
        public T Value => (T)target?.Target;

        public override bool Equals(object obj)
        {
            if (obj == null)
                return !IsAlive;
            else if (obj is T t)
                return Value?.Equals(t) ?? false;
            else if (obj is Weak<T> w)
                return Value?.Equals(w.Value) ?? false;
            else
                return false;
        }

        public override int GetHashCode() => Value?.GetHashCode() ?? 0;

        public override string ToString() => Value?.ToString();

        [SecurityCritical]
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue(nameof(Value), Value, typeof(T));
            info.AddValue(nameof(TrackResurrection), TrackResurrection);
        }

        public static implicit operator Weak<T>(T value) => value != null ? new Weak<T>(value) : new Weak<T>();

        public static bool operator ==(Weak<T> x, Weak<T> y) => x.Equals(y);
        public static bool operator !=(Weak<T> x, Weak<T> y) => !x.Equals(y);
        public static bool operator ==(Weak<T> x, T y) => x.Equals(y);
        public static bool operator !=(Weak<T> x, T y) => !x.Equals(y);
    }
}
