// <auto-generated/>
/*
The MIT License (MIT)

Copyright (c) 2016-2020 Maksim Volkau

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included 
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

// ReSharper disable once InconsistentNaming

#if !NET35 && !PCL
#define SUPPORTS_SPIN_WAIT
#endif

namespace ImTools
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Diagnostics;
    using System.Runtime.CompilerServices; // For [MethodImpl(AggressiveInlining)]

    /// <summary>Helpers for functional composition</summary>
    public static class Fun
    {
        /// <summary>
        /// Always a true condition.
        /// </summary> 
        public static bool Always<T>(T _) => true;

        /// <summary>
        /// Identity function returning passed argument as result.
        /// </summary> 
        public static T Id<T>(T x) => x;

        /// <summary>
        /// Forward pipe operator (`|>` in F#)
        /// </summary> 
        public static R To<T, R>(this T x, Func<T, R> map) => map(x);

        /// <summary>
        /// Forward pipe operator (`|>` in F#) with the additional state A for two arguments function
        /// </summary> 
        public static R To<T, S, R>(this T x, S state, Func<T, S, R> map) => map(x, state);

        /// <summary>
        /// Cast to the R type with the forward pipe operator (`|>` in F#)
        /// </summary> 
        public static R To<R>(this object x) => (R)x;

        /// <summary>
        /// Forward pipe operator (`|>` in F#) but with side effect propagating the original `x` value
        /// </summary> 
        public static T Do<T>(this T x, Action<T> effect)
        {
            effect(x);
            return x;
        }

        /// <summary>
        /// Forward pipe operator (`|>` in F#) but with side effect propagating the original `x` value and the state object
        /// </summary> 
        public static T Do<T, S>(this T x, S state, Action<T, S> effect)
        {
            effect(x, state);
            return x;
        }

        /// <summary>
        /// Lifts argument to Func without allocations ignoring the first argument.
        /// For example if you have `Func{T, R} = _ => instance`,
        /// you may rewrite it without allocations as `instance.ToFunc{A, R}`
        /// </summary> 
        public static R ToFunc<T, R>(this R result, T ignoredArg) => result;
    }

    /// <summary>Helpers for lazy instantiations</summary>
    public static class Lazy
    {
        /// <summary>Provides result type inference for creation of lazy.</summary>
        public static Lazy<T> Of<T>(Func<T> valueFactory) => new Lazy<T>(valueFactory);
    }

    /// Replacement for `Void` type which can be used as a type argument and value.
    /// In traditional functional languages this type is a singleton empty record type,
    /// e.g. `()` in Haskell https://en.wikipedia.org/wiki/Unit_type
    public struct Unit : IEquatable<Unit>
    {
        /// Singleton unit value - making it a lower-case so you could import `using static ImTools.Unit;` and write `return unit;`
        public static readonly Unit unit = new Unit();

        /// <inheritdoc />
        public override string ToString() => "(unit)";

        /// Equals to any other Unit
        public bool Equals(Unit other) => true;

        /// <inheritdoc />
        public override bool Equals(object obj) => obj is Unit;

        /// Using type hash code for the value
        public override int GetHashCode() => typeof(Unit).GetHashCode();
    }

    /// Simple value provider interface - useful for the type pattern matching via `case I{T} x: ...`
    public interface I<out T>
    {
        /// The value in this case ;)
        T Value { get; }
    }

    /// Helpers for `Is` and `Union`
    public static class UnionTools
    {
        /// Pretty prints the Union using the type information
        internal static string ToString<TName, T>(T value, string prefix = "case(", string suffix = ")")
        {
            if (typeof(TName) == typeof(Unit))
                return prefix + value + suffix;

            var typeName = typeof(TName).Name;
            var i = typeName.IndexOf('`');
            var name = i == -1 ? typeName : typeName.Substring(0, i);
            return name + prefix + value + suffix;
        }
    }

    /// Wraps the `T` in a typed `TData` struct value in a one-line declaration,
    /// so the <c><![CDATA[class Name : Case<Name, string>]]></c>
    /// is different from the <c><![CDATA[class Address : Case<Address, string>]]></c> 
    public abstract class Item<TItem, T> where TItem : Item<TItem, T>
    {
        /// Creation method for the consistency with other types
        public static item Of(T x) => new item(x);

        /// Nested structure that hosts a value.
        /// All nested types by convention here are lowercase
        public readonly struct item : IEquatable<item>, I<T>
        {
            /// <inheritdoc />
            public T Value { [MethodImpl((MethodImplOptions)256)] get => Item; }

            /// The value
            public readonly T Item;

            /// Constructor
            public item(T x) => Item = x;

            /// <inheritdoc />
            public bool Equals(item other) => EqualityComparer<T>.Default.Equals(Value, other.Value);

            /// <inheritdoc />
            public override bool Equals(object obj) => obj is item c && Equals(c);

            /// <inheritdoc />
            public override int GetHashCode() => EqualityComparer<T>.Default.GetHashCode(Value);

            /// <inheritdoc />
            public override string ToString() => UnionTools.ToString<TItem, T>(Value);
        }
    }

    /// Item without the data payload
    public abstract class Item<TItem> where TItem : Item<TItem>
    {
        /// Single item value
        public static readonly item Single = new item();

        /// Nested structure that hosts a value.
        /// All nested types by convention here are lowercase
        public readonly struct item : IEquatable<item>
        {
            /// <inheritdoc />
            public bool Equals(item other) => true;

            /// <inheritdoc />
            public override bool Equals(object obj) => obj is item;

            /// <inheritdoc />
            public override int GetHashCode() => typeof(TItem).GetHashCode();

            /// <inheritdoc />
            public override string ToString() => "(" + typeof(TItem).Name + ")";
        }
    }

    /// Wraps the `T` in a named `TBox` class in a one-line declaration,
    /// so the <c><![CDATA[class Name : Data<Name, string>]]></c>
    /// is different from the <c><![CDATA[class Address : Data<Address, string>]]></c> 
    public abstract class Box<TBox, T> : I<T>, IEquatable<Box<TBox, T>>
        where TBox : Box<TBox, T>, new()
    {
        /// Wraps the value
        public static TBox Of(T x) => new TBox { Value = x };

        /// <inheritdoc />
        public T Value { get; private set; }

        /// <inheritdoc />
        public bool Equals(Box<TBox, T> other) =>
            other != null && EqualityComparer<T>.Default.Equals(Value, other.Value);

        /// <inheritdoc />
        public override bool Equals(object obj) => obj is Box<TBox, T> c && Equals(c);

        // ReSharper disable once NonReadonlyMemberInGetHashCode
        /// <inheritdoc />
        public override int GetHashCode() => EqualityComparer<T>.Default.GetHashCode(Value);

        /// <inheritdoc />
        public override string ToString() => UnionTools.ToString<TBox, T>(Value, "data(");
    }

    /// Unnamed discriminated union (with Empty name), shorter name for simplified inline usage
    public class U<T1, T2> : Union<Unit, T1, T2> { }

    /// Discriminated union
    public abstract class Union<TUnion, T1, T2>
    {
        /// To tag the cases with enum value for efficient pattern matching of required -
        /// otherwise we need to use `is CaseN` pattern or similar which is less efficient
        public enum Tag : byte
        {
            /// Tags Case1
            Case1,
            /// Tags Case2
            Case2
        }

        /// The base interface for the cases to operate.
        /// The naming is selected to start from the lower letter, cause we need to use the nested type.
        /// It is an unusual case, that's why using the __union__ will be fine to highlight this.
        // ReSharper disable once InconsistentNaming
        public interface union
        {
            /// The tag
            Tag Tag { get; }

            /// Matches the union cases to the R value
            R Match<R>(Func<T1, R> map1, Func<T2, R> map2);
        }

        /// Creates the respective case
        public static union Of(T1 x) => new case1(x);

        /// Creates the respective case
        public static union Of(T2 x) => new case2(x);

        /// Wraps the respective case
        public readonly struct case1 : union, IEquatable<case1>, I<T1>
        {
            /// Implicit conversion
            public static implicit operator case1(T1 x) => new case1(x);

            /// <inheritdoc />
            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case1; }

            /// <inheritdoc />
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2) => map1(Case);

            /// <inheritdoc />
            public T1 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            /// The case value
            public readonly T1 Case;

            /// Wraps the value
            public case1(T1 x) => Case = x;

            /// <inheritdoc />
            public bool Equals(case1 other) => EqualityComparer<T1>.Default.Equals(Value, other.Value);

            /// <inheritdoc />
            public override bool Equals(object obj) => obj is case1 x && Equals(x);

            /// <inheritdoc />
            public override int GetHashCode() => EqualityComparer<T1>.Default.GetHashCode(Value);

            /// <inheritdoc />
            public override string ToString() => UnionTools.ToString<TUnion, T1>(Value);
        }

        /// Wraps the respective case
        public readonly struct case2 : union, IEquatable<case2>, I<T2>
        {
            /// Conversion
            public static implicit operator case2(T2 x) => new case2(x);

            /// <inheritdoc />
            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case2; }

            /// <inheritdoc />
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2) => map2(Value);

            /// <inheritdoc />
            public T2 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            /// The case value
            public readonly T2 Case;

            /// Wraps the value
            public case2(T2 x) => Case = x;

            /// <inheritdoc />
            public bool Equals(case2 other) => EqualityComparer<T2>.Default.Equals(Value, other.Value);

            /// <inheritdoc />
            public override bool Equals(object obj) => obj is case2 x && Equals(x);

            /// <inheritdoc />
            public override int GetHashCode() => EqualityComparer<T2>.Default.GetHashCode(Value);

            /// <inheritdoc />
            public override string ToString() => UnionTools.ToString<TUnion, T2>(Value);
        }
    }

#pragma warning disable 1591
    public class U<T1, T2, T3> : Union<Unit, T1, T2, T3> { }

    public abstract class Union<TUnion, T1, T2, T3>
    {
        public enum Tag : byte { Case1, Case2, Case3 }

        public interface union
        {
            Tag Tag { get; }
            R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3);
        }

        public static union Of(T1 x) => new case1(x);
        public static union Of(T2 x) => new case2(x);
        public static union Of(T3 x) => new case3(x);

        public struct case1 : union, IEquatable<case1>, I<T1>
        {
            public static implicit operator case1(T1 x) => new case1(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case1; }

            [MethodImpl((MethodImplOptions)256)]
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3) => map1(Case);

            public T1 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T1 Case;
            public case1(T1 x) => Case = x;

            public bool Equals(case1 other) => EqualityComparer<T1>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case1 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T1>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T1>(Case);
        }

        public struct case2 : union, IEquatable<case2>, I<T2>
        {
            public static implicit operator case2(T2 x) => new case2(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case2; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3) => map2(Case);

            public T2 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T2 Case;
            public case2(T2 x) => Case = x;

            public bool Equals(case2 other) => EqualityComparer<T2>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case2 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T2>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T2>(Case);
        }

        public struct case3 : union, IEquatable<case3>, I<T3>
        {
            public static implicit operator case3(T3 x) => new case3(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case3; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3) => map3(Case);

            public T3 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T3 Case;
            public case3(T3 x) => Case = x;

            public bool Equals(case3 other) => EqualityComparer<T3>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case3 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T3>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T3>(Case);
        }
    }

    public class U<T1, T2, T3, T4> : Union<Unit, T1, T2, T3, T4> { }
    public abstract class Union<TUnion, T1, T2, T3, T4>
    {
        public enum Tag : byte { Case1, Case2, Case3, Case4 }

        public interface union
        {
            Tag Tag { get; }
            R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4);
        }

        public static union Of(T1 x) => new case1(x);
        public static union Of(T2 x) => new case2(x);
        public static union Of(T3 x) => new case3(x);
        public static union Of(T4 x) => new case4(x);

        public struct case1 : union, IEquatable<case1>, I<T1>
        {
            public static implicit operator case1(T1 x) => new case1(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case1; }

            [MethodImpl((MethodImplOptions)256)]
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4) => map1(Case);

            public T1 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T1 Case;
            public case1(T1 x) => Case = x;

            public bool Equals(case1 other) => EqualityComparer<T1>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case1 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T1>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T1>(Case);
        }

        public struct case2 : union, IEquatable<case2>, I<T2>
        {
            public static implicit operator case2(T2 x) => new case2(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case2; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4) => map2(Case);

            public T2 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T2 Case;
            public case2(T2 x) => Case = x;

            public bool Equals(case2 other) => EqualityComparer<T2>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case2 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T2>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T2>(Case);
        }

        public struct case3 : union, IEquatable<case3>, I<T3>
        {
            public static implicit operator case3(T3 x) => new case3(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case3; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4) => map3(Case);

            public T3 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T3 Case;
            public case3(T3 x) => Case = x;

            public bool Equals(case3 other) => EqualityComparer<T3>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case3 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T3>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T3>(Case);
        }

        public struct case4 : union, IEquatable<case4>, I<T4>
        {
            public static implicit operator case4(T4 x) => new case4(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case4; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4) => map4(Case);

            public T4 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T4 Case;
            public case4(T4 x) => Case = x;

            public bool Equals(case4 other) => EqualityComparer<T4>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case4 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T4>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T4>(Case);
        }
    }

    public class U<T1, T2, T3, T4, T5> : Union<Unit, T1, T2, T3, T4, T5> { }
    public abstract class Union<TUnion, T1, T2, T3, T4, T5>
    {
        public enum Tag : byte { Case1, Case2, Case3, Case4, Case5 }

        public interface union
        {
            Tag Tag { get; }
            R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5);
        }

        public static union Of(T1 x) => new case1(x);
        public static union Of(T2 x) => new case2(x);
        public static union Of(T3 x) => new case3(x);
        public static union Of(T4 x) => new case4(x);
        public static union Of(T5 x) => new case5(x);

        public struct case1 : union, IEquatable<case1>, I<T1>
        {
            public static implicit operator case1(T1 x) => new case1(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case1; }

            [MethodImpl((MethodImplOptions)256)]
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5) => map1(Case);

            public T1 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T1 Case;
            public case1(T1 x) => Case = x;

            public bool Equals(case1 other) => EqualityComparer<T1>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case1 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T1>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T1>(Case);
        }

        public struct case2 : union, IEquatable<case2>, I<T2>
        {
            public static implicit operator case2(T2 x) => new case2(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case2; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5) => map2(Case);

            public T2 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T2 Case;
            public case2(T2 x) => Case = x;

            public bool Equals(case2 other) => EqualityComparer<T2>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case2 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T2>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T2>(Case);
        }

        public struct case3 : union, IEquatable<case3>, I<T3>
        {
            public static implicit operator case3(T3 x) => new case3(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case3; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5) => map3(Case);

            public T3 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T3 Case;
            public case3(T3 x) => Case = x;

            public bool Equals(case3 other) => EqualityComparer<T3>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case3 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T3>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T3>(Case);
        }

        public struct case4 : union, IEquatable<case4>, I<T4>
        {
            public static implicit operator case4(T4 x) => new case4(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case4; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5) => map4(Case);

            public T4 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T4 Case;
            public case4(T4 x) => Case = x;

            public bool Equals(case4 other) => EqualityComparer<T4>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case4 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T4>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T4>(Case);
        }

        public struct case5 : union, IEquatable<case5>, I<T5>
        {
            public static implicit operator case5(T5 x) => new case5(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case5; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5) => map5(Case);

            public T5 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T5 Case;
            public case5(T5 x) => Case = x;

            public bool Equals(case5 other) => EqualityComparer<T5>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case5 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T5>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T5>(Case);
        }
    }

    public class U<T1, T2, T3, T4, T5, T6> : Union<Unit, T1, T2, T3, T4, T5, T6> { }
    public abstract class Union<TUnion, T1, T2, T3, T4, T5, T6>
    {
        public enum Tag : byte { Case1, Case2, Case3, Case4, Case5, Case6 }

        public interface union
        {
            Tag Tag { get; }
            R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6);
        }

        public static union Of(T1 x) => new case1(x);
        public static union Of(T2 x) => new case2(x);
        public static union Of(T3 x) => new case3(x);
        public static union Of(T4 x) => new case4(x);
        public static union Of(T5 x) => new case5(x);
        public static union Of(T6 x) => new case6(x);

        public struct case1 : union, IEquatable<case1>, I<T1>
        {
            public static implicit operator case1(T1 x) => new case1(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case1; }

            [MethodImpl((MethodImplOptions)256)]
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6) => map1(Case);

            public T1 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T1 Case;
            public case1(T1 x) => Case = x;

            public bool Equals(case1 other) => EqualityComparer<T1>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case1 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T1>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T1>(Case);
        }

        public struct case2 : union, IEquatable<case2>, I<T2>
        {
            public static implicit operator case2(T2 x) => new case2(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case2; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6) => map2(Case);

            public T2 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T2 Case;
            public case2(T2 x) => Case = x;

            public bool Equals(case2 other) => EqualityComparer<T2>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case2 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T2>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T2>(Case);
        }

        public struct case3 : union, IEquatable<case3>, I<T3>
        {
            public static implicit operator case3(T3 x) => new case3(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case3; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6) => map3(Case);

            public T3 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T3 Case;
            public case3(T3 x) => Case = x;

            public bool Equals(case3 other) => EqualityComparer<T3>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case3 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T3>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T3>(Case);
        }

        public struct case4 : union, IEquatable<case4>, I<T4>
        {
            public static implicit operator case4(T4 x) => new case4(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case4; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6) => map4(Case);

            public T4 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T4 Case;
            public case4(T4 x) => Case = x;

            public bool Equals(case4 other) => EqualityComparer<T4>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case4 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T4>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T4>(Case);
        }

        public struct case5 : union, IEquatable<case5>, I<T5>
        {
            public static implicit operator case5(T5 x) => new case5(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case5; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6) => map5(Case);

            public T5 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T5 Case;
            public case5(T5 x) => Case = x;

            public bool Equals(case5 other) => EqualityComparer<T5>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case5 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T5>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T5>(Case);
        }

        public struct case6 : union, IEquatable<case6>, I<T6>
        {
            public static implicit operator case6(T6 x) => new case6(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case6; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6) => map6(Case);

            public T6 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T6 Case;
            public case6(T6 x) => Case = x;

            public bool Equals(case6 other) => EqualityComparer<T6>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case6 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T6>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T6>(Case);
        }
    }

    public class U<T1, T2, T3, T4, T5, T6, T7> : Union<Unit, T1, T2, T3, T4, T5, T6, T7> { }
    public abstract class Union<TUnion, T1, T2, T3, T4, T5, T6, T7>
    {
        public enum Tag : byte { Case1, Case2, Case3, Case4, Case5, Case6, Case7 }

        public interface union
        {
            Tag Tag { get; }
            R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7);
        }

        public static union Of(T1 x) => new case1(x);
        public static union Of(T2 x) => new case2(x);
        public static union Of(T3 x) => new case3(x);
        public static union Of(T4 x) => new case4(x);
        public static union Of(T5 x) => new case5(x);
        public static union Of(T6 x) => new case6(x);
        public static union Of(T7 x) => new case7(x);

        public struct case1 : union, IEquatable<case1>, I<T1>
        {
            public static implicit operator case1(T1 x) => new case1(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case1; }

            [MethodImpl((MethodImplOptions)256)]
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7) => map1(Case);

            public T1 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T1 Case;
            public case1(T1 x) => Case = x;

            public bool Equals(case1 other) => EqualityComparer<T1>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case1 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T1>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T1>(Case);
        }

        public struct case2 : union, IEquatable<case2>, I<T2>
        {
            public static implicit operator case2(T2 x) => new case2(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case2; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7) => map2(Case);

            public T2 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T2 Case;
            public case2(T2 x) => Case = x;

            public bool Equals(case2 other) => EqualityComparer<T2>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case2 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T2>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T2>(Case);
        }

        public struct case3 : union, IEquatable<case3>, I<T3>
        {
            public static implicit operator case3(T3 x) => new case3(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case3; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7) => map3(Case);

            public T3 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T3 Case;
            public case3(T3 x) => Case = x;

            public bool Equals(case3 other) => EqualityComparer<T3>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case3 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T3>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T3>(Case);
        }

        public struct case4 : union, IEquatable<case4>, I<T4>
        {
            public static implicit operator case4(T4 x) => new case4(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case4; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7) => map4(Case);

            public T4 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T4 Case;
            public case4(T4 x) => Case = x;

            public bool Equals(case4 other) => EqualityComparer<T4>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case4 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T4>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T4>(Case);
        }

        public struct case5 : union, IEquatable<case5>, I<T5>
        {
            public static implicit operator case5(T5 x) => new case5(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case5; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7) => map5(Case);

            public T5 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T5 Case;
            public case5(T5 x) => Case = x;

            public bool Equals(case5 other) => EqualityComparer<T5>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case5 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T5>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T5>(Case);
        }

        public struct case6 : union, IEquatable<case6>, I<T6>
        {
            public static implicit operator case6(T6 x) => new case6(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case6; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7) => map6(Case);

            public T6 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T6 Case;
            public case6(T6 x) => Case = x;

            public bool Equals(case6 other) => EqualityComparer<T6>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case6 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T6>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T6>(Case);
        }

        public struct case7 : union, IEquatable<case7>, I<T7>
        {
            public static implicit operator case7(T7 x) => new case7(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case7; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7) => map7(Case);

            public T7 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T7 Case;
            public case7(T7 x) => Case = x;

            public bool Equals(case7 other) => EqualityComparer<T7>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case7 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T7>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T7>(Case);
        }
    }

    public abstract class Union<TUnion, T1, T2, T3, T4, T5, T6, T7, T8>
    {
        public enum Tag : byte { Case1, Case2, Case3, Case4, Case5, Case6, Case7, Case8 }

        public interface union
        {
            Tag Tag { get; }
            R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8);
        }

        public static union Of(T1 x) => new case1(x);
        public static union Of(T2 x) => new case2(x);
        public static union Of(T3 x) => new case3(x);
        public static union Of(T4 x) => new case4(x);
        public static union Of(T5 x) => new case5(x);
        public static union Of(T6 x) => new case6(x);
        public static union Of(T7 x) => new case7(x);
        public static union Of(T8 x) => new case8(x);

        public struct case1 : union, IEquatable<case1>, I<T1>
        {
            public static implicit operator case1(T1 x) => new case1(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case1; }

            [MethodImpl((MethodImplOptions)256)]
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8) => map1(Case);

            public T1 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T1 Case;
            public case1(T1 x) => Case = x;

            public bool Equals(case1 other) => EqualityComparer<T1>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case1 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T1>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T1>(Case);
        }

        public struct case2 : union, IEquatable<case2>, I<T2>
        {
            public static implicit operator case2(T2 x) => new case2(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case2; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8) => map2(Case);

            public T2 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T2 Case;
            public case2(T2 x) => Case = x;

            public bool Equals(case2 other) => EqualityComparer<T2>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case2 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T2>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T2>(Case);
        }

        public struct case3 : union, IEquatable<case3>, I<T3>
        {
            public static implicit operator case3(T3 x) => new case3(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case3; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8) => map3(Case);

            public T3 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T3 Case;
            public case3(T3 x) => Case = x;

            public bool Equals(case3 other) => EqualityComparer<T3>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case3 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T3>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T3>(Case);
        }

        public struct case4 : union, IEquatable<case4>, I<T4>
        {
            public static implicit operator case4(T4 x) => new case4(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case4; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8) => map4(Case);

            public T4 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T4 Case;
            public case4(T4 x) => Case = x;

            public bool Equals(case4 other) => EqualityComparer<T4>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case4 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T4>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T4>(Case);
        }

        public struct case5 : union, IEquatable<case5>, I<T5>
        {
            public static implicit operator case5(T5 x) => new case5(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case5; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8) => map5(Case);

            public T5 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T5 Case;
            public case5(T5 x) => Case = x;

            public bool Equals(case5 other) => EqualityComparer<T5>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case5 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T5>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T5>(Case);
        }

        public struct case6 : union, IEquatable<case6>, I<T6>
        {
            public static implicit operator case6(T6 x) => new case6(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case6; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8) => map6(Case);

            public T6 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T6 Case;
            public case6(T6 x) => Case = x;

            public bool Equals(case6 other) => EqualityComparer<T6>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case6 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T6>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T6>(Case);
        }

        public struct case7 : union, IEquatable<case7>, I<T7>
        {
            public static implicit operator case7(T7 x) => new case7(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case7; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8) => map7(Case);

            public T7 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T7 Case;
            public case7(T7 x) => Case = x;

            public bool Equals(case7 other) => EqualityComparer<T7>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case7 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T7>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T7>(Case);
        }

        public struct case8 : union, IEquatable<case8>, I<T8>
        {
            public static implicit operator case8(T8 x) => new case8(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case8; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5, Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8) => map8(Case);

            public T8 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T8 Case;
            public case8(T8 x) => Case = x;

            public bool Equals(case8 other) => EqualityComparer<T8>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case8 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T8>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T8>(Case);
        }
    }

    public abstract class Union<TUnion, T1, T2, T3, T4, T5, T6, T7, T8, T9>
    {
        public enum Tag : byte { Case1, Case2, Case3, Case4, Case5, Case6, Case7, Case8, Case9 }

        public interface union
        {
            Tag Tag { get; }
            R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9);
        }

        public static union Of(T1 x) => new case1(x);
        public static union Of(T2 x) => new case2(x);
        public static union Of(T3 x) => new case3(x);
        public static union Of(T4 x) => new case4(x);
        public static union Of(T5 x) => new case5(x);
        public static union Of(T6 x) => new case6(x);
        public static union Of(T7 x) => new case7(x);
        public static union Of(T8 x) => new case8(x);
        public static union Of(T9 x) => new case9(x);

        public struct case1 : union, IEquatable<case1>, I<T1>
        {
            public static implicit operator case1(T1 x) => new case1(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case1; }

            [MethodImpl((MethodImplOptions)256)]
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9) => map1(Case);

            public T1 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T1 Case;
            public case1(T1 x) => Case = x;

            public bool Equals(case1 other) => EqualityComparer<T1>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case1 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T1>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T1>(Case);
        }

        public struct case2 : union, IEquatable<case2>, I<T2>
        {
            public static implicit operator case2(T2 x) => new case2(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case2; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9) => map2(Case);

            public T2 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T2 Case;
            public case2(T2 x) => Case = x;

            public bool Equals(case2 other) => EqualityComparer<T2>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case2 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T2>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T2>(Case);
        }

        public struct case3 : union, IEquatable<case3>, I<T3>
        {
            public static implicit operator case3(T3 x) => new case3(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case3; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9) => map3(Case);

            public T3 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T3 Case;
            public case3(T3 x) => Case = x;

            public bool Equals(case3 other) => EqualityComparer<T3>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case3 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T3>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T3>(Case);
        }

        public struct case4 : union, IEquatable<case4>, I<T4>
        {
            public static implicit operator case4(T4 x) => new case4(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case4; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9) => map4(Case);

            public T4 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T4 Case;
            public case4(T4 x) => Case = x;

            public bool Equals(case4 other) => EqualityComparer<T4>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case4 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T4>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T4>(Case);
        }

        public struct case5 : union, IEquatable<case5>, I<T5>
        {
            public static implicit operator case5(T5 x) => new case5(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case5; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9) => map5(Case);

            public T5 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T5 Case;
            public case5(T5 x) => Case = x;

            public bool Equals(case5 other) => EqualityComparer<T5>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case5 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T5>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T5>(Case);
        }

        public struct case6 : union, IEquatable<case6>, I<T6>
        {
            public static implicit operator case6(T6 x) => new case6(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case6; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9) => map6(Case);

            public T6 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T6 Case;
            public case6(T6 x) => Case = x;

            public bool Equals(case6 other) => EqualityComparer<T6>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case6 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T6>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T6>(Case);
        }

        public struct case7 : union, IEquatable<case7>, I<T7>
        {
            public static implicit operator case7(T7 x) => new case7(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case7; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9) => map7(Case);

            public T7 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T7 Case;
            public case7(T7 x) => Case = x;

            public bool Equals(case7 other) => EqualityComparer<T7>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case7 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T7>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T7>(Case);
        }

        public struct case8 : union, IEquatable<case8>, I<T8>
        {
            public static implicit operator case8(T8 x) => new case8(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case8; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9) => map8(Case);

            public T8 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T8 Case;
            public case8(T8 x) => Case = x;

            public bool Equals(case8 other) => EqualityComparer<T8>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case8 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T8>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T8>(Case);
        }

        public struct case9 : union, IEquatable<case9>, I<T9>
        {
            public static implicit operator case9(T9 x) => new case9(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case9; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9) => map9(Case);

            public T9 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T9 Case;
            public case9(T9 x) => Case = x;

            public bool Equals(case9 other) => EqualityComparer<T9>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case9 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T9>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T9>(Case);
        }
    }

    public abstract class Union<TUnion, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
    {
        public enum Tag : byte { Case1, Case2, Case3, Case4, Case5, Case6, Case7, Case8, Case9, Case10 }

        public interface union
        {
            Tag Tag { get; }
            R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9, Func<T10, R> map10);
        }

        public static union Of(T1 x) => new case1(x);
        public static union Of(T2 x) => new case2(x);
        public static union Of(T3 x) => new case3(x);
        public static union Of(T4 x) => new case4(x);
        public static union Of(T5 x) => new case5(x);
        public static union Of(T6 x) => new case6(x);
        public static union Of(T7 x) => new case7(x);
        public static union Of(T8 x) => new case8(x);
        public static union Of(T9 x) => new case9(x);
        public static union Of(T10 x) => new case10(x);

        public struct case1 : union, IEquatable<case1>, I<T1>
        {
            public static implicit operator case1(T1 x) => new case1(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case1; }

            [MethodImpl((MethodImplOptions)256)]
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9, Func<T10, R> map10) => map1(Case);

            public T1 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T1 Case;
            public case1(T1 x) => Case = x;

            public bool Equals(case1 other) => EqualityComparer<T1>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case1 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T1>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T1>(Case);
        }

        public struct case2 : union, IEquatable<case2>, I<T2>
        {
            public static implicit operator case2(T2 x) => new case2(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case2; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9, Func<T10, R> map10) => map2(Case);

            public T2 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T2 Case;
            public case2(T2 x) => Case = x;

            public bool Equals(case2 other) => EqualityComparer<T2>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case2 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T2>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T2>(Case);
        }

        public struct case3 : union, IEquatable<case3>, I<T3>
        {
            public static implicit operator case3(T3 x) => new case3(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case3; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9, Func<T10, R> map10) => map3(Case);

            public T3 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T3 Case;
            public case3(T3 x) => Case = x;

            public bool Equals(case3 other) => EqualityComparer<T3>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case3 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T3>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T3>(Case);
        }

        public struct case4 : union, IEquatable<case4>, I<T4>
        {
            public static implicit operator case4(T4 x) => new case4(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case4; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9, Func<T10, R> map10) => map4(Case);

            public T4 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T4 Case;
            public case4(T4 x) => Case = x;

            public bool Equals(case4 other) => EqualityComparer<T4>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case4 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T4>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T4>(Case);
        }

        public struct case5 : union, IEquatable<case5>, I<T5>
        {
            public static implicit operator case5(T5 x) => new case5(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case5; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9, Func<T10, R> map10) => map5(Case);

            public T5 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T5 Case;
            public case5(T5 x) => Case = x;

            public bool Equals(case5 other) => EqualityComparer<T5>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case5 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T5>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T5>(Case);
        }

        public struct case6 : union, IEquatable<case6>, I<T6>
        {
            public static implicit operator case6(T6 x) => new case6(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case6; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9, Func<T10, R> map10) => map6(Case);

            public T6 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T6 Case;
            public case6(T6 x) => Case = x;

            public bool Equals(case6 other) => EqualityComparer<T6>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case6 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T6>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T6>(Case);
        }

        public struct case7 : union, IEquatable<case7>, I<T7>
        {
            public static implicit operator case7(T7 x) => new case7(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case7; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9, Func<T10, R> map10) => map7(Case);

            public T7 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T7 Case;
            public case7(T7 x) => Case = x;

            public bool Equals(case7 other) => EqualityComparer<T7>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case7 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T7>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T7>(Case);
        }

        public struct case8 : union, IEquatable<case8>, I<T8>
        {
            public static implicit operator case8(T8 x) => new case8(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case8; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9, Func<T10, R> map10) => map8(Case);

            public T8 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T8 Case;
            public case8(T8 x) => Case = x;

            public bool Equals(case8 other) => EqualityComparer<T8>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case8 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T8>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T8>(Case);
        }

        public struct case9 : union, IEquatable<case9>, I<T9>
        {
            public static implicit operator case9(T9 x) => new case9(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case9; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9, Func<T10, R> map10) => map9(Case);

            public T9 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T9 Case;
            public case9(T9 x) => Case = x;

            public bool Equals(case9 other) => EqualityComparer<T9>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case9 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T9>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T9>(Case);
        }

        public struct case10 : union, IEquatable<case10>, I<T10>
        {
            public static implicit operator case10(T10 x) => new case10(x);

            public Tag Tag { [MethodImpl((MethodImplOptions)256)] get => Tag.Case10; }
            public R Match<R>(Func<T1, R> map1, Func<T2, R> map2, Func<T3, R> map3, Func<T4, R> map4, Func<T5, R> map5,
                Func<T6, R> map6, Func<T7, R> map7, Func<T8, R> map8, Func<T9, R> map9, Func<T10, R> map10) => map10(Case);

            public T10 Value { [MethodImpl((MethodImplOptions)256)] get => Case; }

            public readonly T10 Case;
            public case10(T10 x) => Case = x;

            public bool Equals(case10 other) => EqualityComparer<T10>.Default.Equals(Case, other.Case);
            public override bool Equals(object obj) => obj is case10 x && Equals(x);
            public override int GetHashCode() => EqualityComparer<T10>.Default.GetHashCode(Case);
            public override string ToString() => UnionTools.ToString<TUnion, T10>(Case);
        }
    }

#pragma warning restore 1591

    /// <summary>Methods to work with immutable arrays and some sugar.</summary>
    public static class ArrayTools
    {
        private static class EmptyArray<T>
        {
            public static readonly T[] Value = new T[0];
        }

        /// <summary>Returns singleton empty array of provided type.</summary> 
        /// <typeparam name="T">Array item type.</typeparam> <returns>Empty array.</returns>
        public static T[] Empty<T>() => EmptyArray<T>.Value;

        /// <summary>Wraps item in array.</summary>
        public static T[] One<T>(this T one) => new[] { one };

        /// <summary>Returns true if array is null or have no items.</summary> <typeparam name="T">Type of array item.</typeparam>
        /// <param name="source">Source array to check.</param> <returns>True if null or has no items, false otherwise.</returns>
        public static bool IsNullOrEmpty<T>(this T[] source) => source == null || source.Length == 0;

        /// <summary>Returns empty array instead of null, or source array otherwise.</summary> <typeparam name="T">Type of array item.</typeparam>
        public static T[] EmptyIfNull<T>(this T[] source) => source ?? Empty<T>();

        /// Returns source enumerable if it is array, otherwise converts source to array or an empty array if null.
        public static T[] ToArrayOrSelf<T>(this IEnumerable<T> source) =>
            source == null ? Empty<T>() : (source as T[] ?? source.ToArray());

        /// Returns source enumerable if it is list, otherwise converts source to IList or an empty array if null.
        public static IList<T> ToListOrSelf<T>(this IEnumerable<T> source) =>
            source == null ? Empty<T>() : source as IList<T> ?? source.ToList();

        /// <summary>
        /// Array copy
        /// </summary>
        public static T[] Copy<T>(this T[] items)
        {
            if (items == null)
                return null;
            var copy = new T[items.Length];
            for (var i = 0; i < copy.Length; i++)
                copy[i] = items[i];
            return copy;
        }

        /// <summary>Returns new array consisting from all items from source array then all items from added array.
        /// If source is null or empty, then added array will be returned.
        /// If added is null or empty, then source will be returned.</summary>
        /// <typeparam name="T">Array item type.</typeparam>
        /// <param name="source">Array with leading items.</param>
        /// <param name="added">Array with following items.</param>
        /// <returns>New array with items of source and added arrays.</returns>
        public static T[] Append<T>(this T[] source, params T[] added)
        {
            if (added == null || added.Length == 0)
                return source;
            if (source == null || source.Length == 0)
                return added;

            var result = new T[source.Length + added.Length];
            Array.Copy(source, 0, result, 0, source.Length);
            if (added.Length == 1)
                result[source.Length] = added[0];
            else
                Array.Copy(added, 0, result, source.Length, added.Length);
            return result;
        }

        /// <summary>Performant concat of enumerables in case of arrays.
        /// But performance will degrade if you use Concat().Where().</summary>
        /// <typeparam name="T">Type of item.</typeparam>
        /// <param name="source">goes first.</param>
        /// <param name="other">appended to source.</param>
        /// <returns>empty array or concat of source and other.</returns>
        public static T[] Append<T>(this IEnumerable<T> source, IEnumerable<T> other) =>
            source.ToArrayOrSelf().Append(other.ToArrayOrSelf());

        /// <summary>Returns new array with <paramref name="value"/> appended, 
        /// or <paramref name="value"/> at <paramref name="index"/>, if specified.
        /// If source array could be null or empty, then single value item array will be created despite any index.</summary>
        /// <typeparam name="T">Array item type.</typeparam>
        /// <param name="source">Array to append value to.</param>
        /// <param name="value">Value to append.</param>
        /// <param name="index">(optional) Index of value to update.</param>
        /// <returns>New array with appended or updated value.</returns>
        public static T[] AppendOrUpdate<T>(this T[] source, T value, int index = -1)
        {
            if (source == null || source.Length == 0)
                return new[] { value };
            var sourceLength = source.Length;
            index = index < 0 ? sourceLength : index;
            var result = new T[index < sourceLength ? sourceLength : sourceLength + 1];
            Array.Copy(source, result, sourceLength);
            result[index] = value;
            return result;
        }

        /// <summary>Calls predicate on each item in <paramref name="source"/> array until predicate returns true,
        /// then method will return this item index, or if predicate returns false for each item, method will return -1.</summary>
        /// <typeparam name="T">Type of array items.</typeparam>
        /// <param name="source">Source array: if null or empty, then method will return -1.</param>
        /// <param name="predicate">Delegate to evaluate on each array item until delegate returns true.</param>
        /// <returns>Index of item for which predicate returns true, or -1 otherwise.</returns>
        public static int IndexOf<T>(this T[] source, Func<T, bool> predicate)
        {
            if (source != null && source.Length != 0)
                for (var i = 0; i < source.Length; ++i)
                    if (predicate(source[i]))
                        return i;
            return -1;
        }

        /// Minimizes the allocations for closure in predicate lambda with the provided <paramref name="state"/>
        public static int IndexOf<T, S>(this T[] source, S state, Func<S, T, bool> predicate)
        {
            if (source != null && source.Length != 0)
                for (var i = 0; i < source.Length; ++i)
                    if (predicate(state, source[i]))
                        return i;
            return -1;
        }

        /// <summary>Looks up for item in source array equal to provided value, and returns its index, or -1 if not found.</summary>
        /// <typeparam name="T">Type of array items.</typeparam>
        /// <param name="source">Source array: if null or empty, then method will return -1.</param>
        /// <param name="value">Value to look up.</param>
        /// <returns>Index of item equal to value, or -1 item is not found.</returns>
        public static int IndexOf<T>(this T[] source, T value)
        {
            if (source != null && source.Length != 0)
                for (var i = 0; i < source.Length; ++i)
                    if (Equals(source[i], value))
                        return i;

            return -1;
        }

        /// <summary>The same as `IndexOf` but searching the item by reference</summary>
        public static int IndexOfReference<T>(this T[] source, T reference) where T : class
        {
            if (source != null && source.Length != 0)
                for (var i = 0; i < source.Length; ++i)
                    if (ReferenceEquals(source[i], reference))
                        return i;

            return -1;
        }

        /// <summary>Produces new array without item at specified <paramref name="index"/>. 
        /// Will return <paramref name="source"/> array if index is out of bounds, or source is null/empty.</summary>
        /// <typeparam name="T">Type of array item.</typeparam>
        /// <param name="source">Input array.</param> <param name="index">Index if item to remove.</param>
        /// <returns>New array with removed item at index, or input source array if index is not in array.</returns>
        public static T[] RemoveAt<T>(this T[] source, int index)
        {
            if (source == null || source.Length == 0 || index < 0 || index >= source.Length)
                return source;
            if (index == 0 && source.Length == 1)
                return new T[0];
            var result = new T[source.Length - 1];
            if (index != 0)
                Array.Copy(source, 0, result, 0, index);
            if (index != result.Length)
                Array.Copy(source, index + 1, result, index, result.Length - index);
            return result;
        }

        /// <summary>Looks for item in array using equality comparison, and returns new array with found item remove, or original array if not item found.</summary>
        /// <typeparam name="T">Type of array item.</typeparam>
        /// <param name="source">Input array.</param> <param name="value">Value to find and remove.</param>
        /// <returns>New array with value removed or original array if value is not found.</returns>
        public static T[] Remove<T>(this T[] source, T value) =>
            source.RemoveAt(source.IndexOf(value));

        /// <summary>Returns first item matching the <paramref name="predicate"/>, or default item value.</summary>
        /// <typeparam name="T">item type</typeparam>
        /// <param name="source">items collection to search</param>
        /// <param name="predicate">condition to evaluate for each item.</param>
        /// <returns>First item matching condition or default value.</returns>
        public static T FindFirst<T>(this T[] source, Func<T, bool> predicate)
        {
            if (source != null && source.Length != 0)
                for (var i = 0; i < source.Length; ++i)
                {
                    var item = source[i];
                    if (predicate(item))
                        return item;
                }

            return default(T);
        }

        /// Version of FindFirst with the fixed state used by predicate to prevent allocations by predicate lambda closure
        public static T FindFirst<T, S>(this T[] source, S state, Func<S, T, bool> predicate)
        {
            if (source != null && source.Length != 0)
                for (var i = 0; i < source.Length; ++i)
                {
                    var item = source[i];
                    if (predicate(state, item))
                        return item;
                }

            return default(T);
        }

        /// <summary>Returns first item matching the <paramref name="predicate"/>, or default item value.</summary>
        /// <typeparam name="T">item type</typeparam>
        /// <param name="source">items collection to search</param>
        /// <param name="predicate">condition to evaluate for each item.</param>
        /// <returns>First item matching condition or default value.</returns>
        public static T FindFirst<T>(this IEnumerable<T> source, Func<T, bool> predicate) =>
            source is T[] sourceArr ? sourceArr.FindFirst(predicate) : source.FirstOrDefault(predicate);

        /// <summary>Returns element if collection consist on single element, otherwise returns default value.
        /// It does not throw for collection with many elements</summary>
        public static T SingleOrDefaultIfMany<T>(this IEnumerable<T> source)
        {
            if (source is IList<T> list)
                return list.Count == 1 ? list[0] : default(T);

            if (source == null)
                return default(T);

            using (var e = source.GetEnumerator())
            {
                if (!e.MoveNext())
                    return default(T);
                var it = e.Current;
                return !e.MoveNext() ? it : default(T);
            }
        }

        /// <summary>Does <paramref name="action"/> for each item</summary>
        public static void ForEach<T>(this T[] source, Action<T> action)
        {
            if (!source.IsNullOrEmpty())
                for (var i = 0; i < source.Length; i++)
                    action(source[i]);
        }

        /// Appends source to results
        public static T[] AppendTo<T>(T[] source, int sourcePos, int count, T[] results = null)
        {
            if (results == null)
            {
                var newResults = new T[count];
                if (count == 1)
                    newResults[0] = source[sourcePos];
                else
                    for (int i = 0, j = sourcePos; i < count; ++i, ++j)
                        newResults[i] = source[j];
                return newResults;
            }

            var matchCount = results.Length;
            var appendedResults = new T[matchCount + count];
            if (matchCount == 1)
                appendedResults[0] = results[0];
            else
                Array.Copy(results, 0, appendedResults, 0, matchCount);

            if (count == 1)
                appendedResults[matchCount] = source[sourcePos];
            else
                Array.Copy(source, sourcePos, appendedResults, matchCount, count);

            return appendedResults;
        }

        private static R[] AppendTo<T, R>(T[] source, int sourcePos, int count, Func<T, R> map, R[] results = null)
        {
            if (results == null || results.Length == 0)
            {
                var newResults = new R[count];
                if (count == 1)
                    newResults[0] = map(source[sourcePos]);
                else
                    for (int i = 0, j = sourcePos; i < count; ++i, ++j)
                        newResults[i] = map(source[j]);
                return newResults;
            }

            var oldResultsCount = results.Length;
            var appendedResults = new R[oldResultsCount + count];
            if (oldResultsCount == 1)
                appendedResults[0] = results[0];
            else
                Array.Copy(results, 0, appendedResults, 0, oldResultsCount);

            if (count == 1)
                appendedResults[oldResultsCount] = map(source[sourcePos]);
            else
            {
                for (int i = oldResultsCount, j = sourcePos; i < appendedResults.Length; ++i, ++j)
                    appendedResults[i] = map(source[j]);
            }

            return appendedResults;
        }

        private static R[] AppendTo<T, S, R>(T[] source, S state, int sourcePos, int count, Func<S, T, R> map, R[] results = null)
        {
            if (results == null || results.Length == 0)
            {
                var newResults = new R[count];
                if (count == 1)
                    newResults[0] = map(state, source[sourcePos]);
                else
                    for (int i = 0, j = sourcePos; i < count; ++i, ++j)
                        newResults[i] = map(state, source[j]);
                return newResults;
            }

            var oldResultsCount = results.Length;
            var appendedResults = new R[oldResultsCount + count];
            if (oldResultsCount == 1)
                appendedResults[0] = results[0];
            else
                Array.Copy(results, 0, appendedResults, 0, oldResultsCount);

            if (count == 1)
                appendedResults[oldResultsCount] = map(state, source[sourcePos]);
            else
            {
                for (int i = oldResultsCount, j = sourcePos; i < appendedResults.Length; ++i, ++j)
                    appendedResults[i] = map(state, source[j]);
            }

            return appendedResults;
        }

        /// <summary>Where method similar to Enumerable.Where but more performant and non necessary allocating.
        /// It returns source array and does Not create new one if all items match the condition.</summary>
        /// <typeparam name="T">Type of source items.</typeparam>
        /// <param name="source">If null, the null will be returned.</param>
        /// <param name="condition">Condition to keep items.</param>
        /// <returns>New array if some items are filter out. Empty array if all items are filtered out. Original array otherwise.</returns>
        public static T[] Match<T>(this T[] source, Func<T, bool> condition)
        {
            if (source == null || source.Length == 0)
                return source;

            if (source.Length == 1)
                return condition(source[0]) ? source : Empty<T>();

            if (source.Length == 2)
            {
                var condition0 = condition(source[0]);
                var condition1 = condition(source[1]);
                return condition0 && condition1 ? new[] { source[0], source[1] }
                    : condition0 ? new[] { source[0] }
                    : condition1 ? new[] { source[1] }
                    : Empty<T>();
            }

            var matchStart = 0;
            T[] matches = null;
            var matchFound = false;
            var i = 0;
            for (; i < source.Length; ++i)
                if (!(matchFound = condition(source[i])))
                {
                    // for accumulated matched items
                    if (i != 0 && i > matchStart)
                        matches = AppendTo(source, matchStart, i - matchStart, matches);
                    matchStart = i + 1; // guess the next match start will be after the non-matched item
                }

            // when last match was found but not all items are matched (hence matchStart != 0)
            if (matchFound && matchStart != 0)
                return AppendTo(source, matchStart, i - matchStart, matches);

            return matches ?? (matchStart != 0 ? Empty<T>() : source);
        }

        /// <summary>The same as `Match` but assumes that <paramref name="source"/> is not null and not empty</summary>
        public static T[] MatchUnsafe<T>(this T[] source, Func<T, bool> condition)
        {
            var matchStart = 0;
            T[] matches = null;
            var matchFound = false;
            var i = 0;
            for (; i < source.Length; ++i)
                if (!(matchFound = condition(source[i])))
                {
                    // for accumulated matched items
                    if (i != 0 && i > matchStart)
                        matches = AppendTo(source, matchStart, i - matchStart, matches);
                    matchStart = i + 1; // guess the next match start will be after the non-matched item
                }

            // when last match was found but not all items are matched (hence matchStart != 0)
            if (matchFound && matchStart != 0)
                return AppendTo(source, matchStart, i - matchStart, matches);

            return matches ?? (matchStart != 0 ? Empty<T>() : source);
        }

        /// Match with the additional state to use in <paramref name="condition"/> to minimize the allocations in <paramref name="condition"/> lambda closure 
        public static T[] Match<T, S>(this T[] source, S state, Func<S, T, bool> condition)
        {
            if (source == null || source.Length == 0)
                return source;

            if (source.Length == 1)
                return condition(state, source[0]) ? source : Empty<T>();

            if (source.Length == 2)
            {
                var condition0 = condition(state, source[0]);
                var condition1 = condition(state, source[1]);
                return condition0 && condition1 ? new[] { source[0], source[1] }
                    : condition0 ? new[] { source[0] }
                    : condition1 ? new[] { source[1] }
                    : Empty<T>();
            }

            var matchStart = 0;
            T[] matches = null;
            var matchFound = false;
            var i = 0;
            for (; i < source.Length; ++i)
                if (!(matchFound = condition(state, source[i])))
                {
                    // for accumulated matched items
                    if (i != 0 && i > matchStart)
                        matches = AppendTo(source, matchStart, i - matchStart, matches);
                    matchStart = i + 1; // guess the next match start will be after the non-matched item
                }

            // when last match was found but not all items are matched (hence matchStart != 0)
            if (matchFound && matchStart != 0)
                return AppendTo(source, matchStart, i - matchStart, matches);

            return matches ?? (matchStart != 0 ? Empty<T>() : source);
        }

        /// <summary>Where method similar to Enumerable.Where but more performant and non necessary allocating.
        /// It returns source array and does Not create new one if all items match the condition.</summary>
        /// <typeparam name="T">Type of source items.</typeparam> <typeparam name="R">Type of result items.</typeparam>
        /// <param name="source">If null, the null will be returned.</param>
        /// <param name="condition">Condition to keep items.</param> <param name="map">Converter from source to result item.</param>
        /// <returns>New array of result items.</returns>
        public static R[] Match<T, R>(this T[] source, Func<T, bool> condition, Func<T, R> map)
        {
            if (source == null)
                return null;

            if (source.Length == 0)
                return Empty<R>();

            if (source.Length == 1)
            {
                var item = source[0];
                return condition(item) ? new[] { map(item) } : Empty<R>();
            }

            if (source.Length == 2)
            {
                var condition0 = condition(source[0]);
                var condition1 = condition(source[1]);
                return condition0 && condition1 ? new[] { map(source[0]), map(source[1]) }
                    : condition0 ? new[] { map(source[0]) }
                    : condition1 ? new[] { map(source[1]) }
                    : Empty<R>();
            }

            var matchStart = 0;
            R[] matches = null;
            var matchFound = false;

            var i = 0;
            for (; i < source.Length; ++i)
                if (!(matchFound = condition(source[i])))
                {
                    // for accumulated matched items
                    if (i != 0 && i > matchStart)
                        matches = AppendTo(source, matchStart, i - matchStart, map, matches);
                    matchStart = i + 1; // guess the next match start will be after the non-matched item
                }

            // when last match was found but not all items are matched (hence matchStart != 0)
            if (matchFound && matchStart != 0)
                return AppendTo(source, matchStart, i - matchStart, map, matches);

            return matches ?? (matchStart == 0 ? AppendTo(source, 0, source.Length, map) : Empty<R>());
        }

        /// Match with the additional state to use in <paramref name="condition"/> and <paramref name="map"/> to minimize the allocations in <paramref name="condition"/> lambda closure 
        public static R[] Match<T, S, R>(this T[] source, S state, Func<S, T, bool> condition, Func<S, T, R> map)
        {
            if (source == null)
                return null;

            if (source.Length == 0)
                return Empty<R>();

            if (source.Length == 1)
            {
                var item = source[0];
                return condition(state, item) ? new[] { map(state, item) } : Empty<R>();
            }

            if (source.Length == 2)
            {
                var condition0 = condition(state, source[0]);
                var condition1 = condition(state, source[1]);
                return condition0 && condition1 ? new[] { map(state, source[0]), map(state, source[1]) }
                    : condition0 ? new[] { map(state, source[0]) }
                    : condition1 ? new[] { map(state, source[1]) }
                    : Empty<R>();
            }

            var matchStart = 0;
            R[] matches = null;
            var matchFound = false;

            var i = 0;
            for (; i < source.Length; ++i)
                if (!(matchFound = condition(state, source[i])))
                {
                    // for accumulated matched items
                    if (i != 0 && i > matchStart)
                        matches = AppendTo(source, state, matchStart, i - matchStart, map, matches);
                    matchStart = i + 1; // guess the next match start will be after the non-matched item
                }

            // when last match was found but not all items are matched (hence matchStart != 0)
            if (matchFound && matchStart != 0)
                return AppendTo(source, state, matchStart, i - matchStart, map, matches);

            return matches ?? (matchStart == 0 ? AppendTo(source, state, 0, source.Length, map) : Empty<R>());
        }

        /// <summary>Maps all items from source to result array.</summary>
        /// <typeparam name="T">Source item type</typeparam> <typeparam name="R">Result item type</typeparam>
        /// <param name="source">Source items</param> <param name="map">Function to convert item from source to result.</param>
        /// <returns>Converted items</returns>
        public static R[] Map<T, R>(this T[] source, Func<T, R> map)
        {
            if (source == null)
                return null;

            var sourceCount = source.Length;
            if (sourceCount == 0)
                return Empty<R>();

            if (sourceCount == 1)
                return new[] { map(source[0]) };

            if (sourceCount == 2)
                return new[] { map(source[0]), map(source[1]) };

            var results = new R[sourceCount];
            for (var i = 0; i < source.Length; i++)
                results[i] = map(source[i]);
            return results;
        }

        /// Map with additional state to use in <paramref name="map"/> to minimize allocations in <paramref name="map"/> lambda closure 
        public static R[] Map<T, S, R>(this T[] source, S state, Func<S, T, R> map)
        {
            if (source == null)
                return null;

            var sourceCount = source.Length;
            if (sourceCount == 0)
                return Empty<R>();

            if (sourceCount == 1)
                return new[] { map(state, source[0]) };

            if (sourceCount == 2)
                return new[] { map(state, source[0]), map(state, source[1]) };

            var results = new R[sourceCount];
            for (var i = 0; i < source.Length; i++)
                results[i] = map(state, source[i]);
            return results;
        }

        /// <summary>Maps all items from source to result collection. 
        /// If possible uses fast array Map otherwise Enumerable.Select.</summary>
        /// <typeparam name="T">Source item type</typeparam> <typeparam name="R">Result item type</typeparam>
        /// <param name="source">Source items</param> <param name="map">Function to convert item from source to result.</param>
        /// <returns>Converted items</returns>
        public static IEnumerable<R> Map<T, R>(this IEnumerable<T> source, Func<T, R> map) =>
            source is T[] arr ? arr.Map(map) : source?.Select(map);

        /// <summary>If <paramref name="source"/> is array uses more effective Match for array, otherwise just calls Where</summary>
        /// <typeparam name="T">Type of source items.</typeparam>
        /// <param name="source">If null, the null will be returned.</param>
        /// <param name="condition">Condition to keep items.</param>
        /// <returns>Result items, may be an array.</returns>
        public static IEnumerable<T> Match<T>(this IEnumerable<T> source, Func<T, bool> condition) =>
            source is T[] arr ? arr.Match(condition) : source?.Where(condition);

        /// <summary>If <paramref name="source"/> is array uses more effective Match for array,
        /// otherwise just calls Where, Select</summary>
        /// <typeparam name="T">Type of source items.</typeparam> <typeparam name="R">Type of result items.</typeparam>
        /// <param name="source">If null, the null will be returned.</param>
        /// <param name="condition">Condition to keep items.</param>  <param name="map">Converter from source to result item.</param>
        /// <returns>Result items, may be an array.</returns>
        public static IEnumerable<R> Match<T, R>(this IEnumerable<T> source, Func<T, bool> condition, Func<T, R> map) =>
            source is T[] arr ? arr.Match(condition, map) : source?.Where(condition).Select(map);
    }

    /// <summary>Wrapper that provides optimistic-concurrency Swap operation implemented using <see cref="Ref.Swap{T}"/>.</summary>
    /// <typeparam name="T">Type of object to wrap.</typeparam>
    public sealed class Ref<T> where T : class
    {
        /// <summary>Gets the wrapped value.</summary>
        public T Value => _value;
        private T _value;

        /// <summary>Creates ref to object, optionally with initial value provided.</summary>
        /// <param name="initialValue">(optional) Initial value.</param>
        public Ref(T initialValue = default) => _value = initialValue;

        /// <summary>Exchanges currently hold object with <paramref name="getNewValue"/> - see <see cref="Ref.Swap{T}"/> for details.</summary>
        /// <param name="getNewValue">Delegate to produce new object value from current one passed as parameter.</param>
        /// <returns>Returns old object value the same way as <see cref="Interlocked.Exchange(ref int,int)"/></returns>
        /// <remarks>Important: <paramref name="getNewValue"/> May be called multiple times to retry update with value concurrently changed by other code.</remarks>
        public T Swap(Func<T, T> getNewValue) =>
            Ref.Swap(ref _value, getNewValue);

        // todo: A good candidate to implement
        // <summary>The same as `Swap` but instead of old known value it returns the new one</summary>
        //public T SwapAndGetNewValue(Func<T, T> getNewValue) =>
        //    Ref.Swap(ref _value, getNewValue);

        /// Option without allocation for capturing `a` in closure of `getNewValue`
        public T Swap<A>(A a, Func<T, A, T> getNewValue) => Ref.Swap(ref _value, a, getNewValue);

        /// Option without allocation for capturing `a` and `b` in closure of `getNewValue`
        public T Swap<A, B>(A a, B b, Func<T, A, B, T> getNewValue) => Ref.Swap(ref _value, a, b, getNewValue);

        /// <summary>Just sets new value ignoring any intermingled changes and returns the original value</summary>
        /// <param name="newValue"></param> <returns>old value</returns>
        public T Swap(T newValue) => Interlocked.Exchange(ref _value, newValue);

        /// <summary>Directly sets the value and returns the new value</summary>
        public T SetNonAtomic(T newValue) => _value = newValue;

        /// <summary>Compares current Referred value with <paramref name="currentValue"/> and if equal replaces current with <paramref name="newValue"/></summary>
        /// <param name="currentValue"></param> <param name="newValue"></param>
        /// <returns>True if current value was replaced with new value, and false if current value is outdated (already changed by other party).</returns>
        /// <example><c>[!CDATA[
        /// var value = SomeRef.Value;
        /// if (!SomeRef.TrySwapIfStillCurrent(value, Update(value))
        ///     SomeRef.Swap(v => Update(v)); // fallback to normal Swap with delegate allocation
        /// ]]</c></example>
        public bool TrySwapIfStillCurrent(T currentValue, T newValue) =>
            Interlocked.CompareExchange(ref _value, newValue, currentValue) == currentValue;
    }

    /// <summary>Provides optimistic-concurrency consistent <see cref="Swap{T}"/> operation.</summary>
    public static class Ref
    {
        /// The default max retry count - can be overriden by `Swap` optional parameter 
        public const int RETRY_COUNT_UNTIL_THROW = 50;

        /// <summary>Factory for <see cref="Ref{T}"/> with type of value inference.</summary>
        /// <typeparam name="T">Type of value to wrap.</typeparam>
        /// <param name="value">Initial value to wrap.</param>
        /// <returns>New ref.</returns>
        public static Ref<T> Of<T>(T value) where T : class => new Ref<T>(value);

        /// <summary>Creates new ref to the value of original ref.</summary> <typeparam name="T">Ref value type.</typeparam>
        /// <param name="original">Original ref.</param> <returns>New ref to original value.</returns>
        public static Ref<T> NewRef<T>(this Ref<T> original) where T : class => Of(original.Value);

        /// <summary>First, it evaluates new value using <paramref name="getNewValue"/> function. 
        /// Second, it checks that original value is not changed. 
        /// If it is changed it will retry first step, otherwise it assigns new value and returns original (the one used for <paramref name="getNewValue"/>).</summary>
        /// <typeparam name="T">Type of value to swap.</typeparam>
        /// <param name="value">Reference to change to new value</param>
        /// <param name="getNewValue">Delegate to get value from old one.</param>
        /// <param name="retryCountUntilThrow">(optional)</param>
        /// <returns>Old/original value. By analogy with <see cref="Interlocked.Exchange(ref int,int)"/>.</returns>
        /// <remarks>Important: <paramref name="getNewValue"/> May be called multiple times to retry update with value concurrently changed by other code.</remarks>
        [MethodImpl((MethodImplOptions)256)]
        public static T Swap<T>(ref T value, Func<T, T> getNewValue,
            int retryCountUntilThrow = RETRY_COUNT_UNTIL_THROW)
            where T : class
        {
#if SUPPORTS_SPIN_WAIT
            var spinWait = new SpinWait();
#endif
            var retryCount = 0;
            while (true)
            {
                var oldValue = value;
                var newValue = getNewValue(oldValue);
                if (Interlocked.CompareExchange(ref value, newValue, oldValue) == oldValue)
                    return oldValue;

                if (++retryCount > retryCountUntilThrow)
                    ThrowRetryCountExceeded(retryCountUntilThrow);
#if SUPPORTS_SPIN_WAIT
                spinWait.SpinOnce();
#endif
            }
        }

        private static void ThrowRetryCountExceeded(int retryCountExceeded) =>
            throw new InvalidOperationException(
                $"Ref retried to Update for {retryCountExceeded} times But there is always someone else intervened.");

        /// <summary>Swap with the additional state <paramref name="a"/> required for the delegate <paramref name="getNewValue"/>.
        /// May prevent closure creation for the delegate</summary>
        [MethodImpl((MethodImplOptions)256)]
        public static T Swap<T, A>(ref T value, A a, Func<T, A, T> getNewValue,
            int retryCountUntilThrow = RETRY_COUNT_UNTIL_THROW)
            where T : class
        {
#if SUPPORTS_SPIN_WAIT
            var spinWait = new SpinWait();
#endif
            var retryCount = 0;
            while (true)
            {
                var oldValue = value;
                var newValue = getNewValue(oldValue, a);
                if (Interlocked.CompareExchange(ref value, newValue, oldValue) == oldValue)
                    return oldValue;
                if (++retryCount > retryCountUntilThrow)
                    ThrowRetryCountExceeded(retryCountUntilThrow);
#if SUPPORTS_SPIN_WAIT
                spinWait.SpinOnce();
#endif
            }
        }

        /// <summary>Swap with the additional state <paramref name="a"/>, <paramref name="b"/> required for the delegate <paramref name="getNewValue"/>.
        /// May prevent closure creation for the delegate</summary>
        [MethodImpl((MethodImplOptions)256)]
        public static T Swap<T, A, B>(ref T value, A a, B b, Func<T, A, B, T> getNewValue,
            int retryCountUntilThrow = RETRY_COUNT_UNTIL_THROW)
            where T : class
        {
#if SUPPORTS_SPIN_WAIT
            var spinWait = new SpinWait();
#endif
            var retryCount = 0;
            while (true)
            {
                var oldValue = value;
                var newValue = getNewValue(oldValue, a, b);
                if (Interlocked.CompareExchange(ref value, newValue, oldValue) == oldValue)
                    return oldValue;

                if (++retryCount > retryCountUntilThrow)
                    ThrowRetryCountExceeded(retryCountUntilThrow);

#if SUPPORTS_SPIN_WAIT
                spinWait.SpinOnce();
#endif
            }
        }

        /// <summary>Swap with the additional state <paramref name="a"/>, <paramref name="b"/>, <paramref name="c"/> required for the delegate <paramref name="getNewValue"/>.
        /// May prevent closure creation for the delegate</summary>
        [MethodImpl((MethodImplOptions)256)]
        public static T Swap<T, A, B, C>(ref T value, A a, B b, C c, Func<T, A, B, C, T> getNewValue,
            int retryCountUntilThrow = RETRY_COUNT_UNTIL_THROW)
            where T : class
        {
#if SUPPORTS_SPIN_WAIT
            var spinWait = new SpinWait();
#endif
            var retryCount = 0;
            while (true)
            {
                var oldValue = value;
                var newValue = getNewValue(oldValue, a, b, c);
                if (Interlocked.CompareExchange(ref value, newValue, oldValue) == oldValue)
                    return oldValue;

                if (++retryCount > retryCountUntilThrow)
                    ThrowRetryCountExceeded(retryCountUntilThrow);

#if SUPPORTS_SPIN_WAIT
                spinWait.SpinOnce();
#endif
            }
        }

        // todo: Func of 5 args is not available on all plats
        //        /// Option without allocation for capturing `a`, `b`, `c`, `d` in closure of `getNewValue`
        //        [MethodImpl((MethodImplOptions)256)]
        //        public static T Swap<T, A, B, C, D>(ref T value, A a, B b, C c, D d, Func<T, A, B, C, D, T> getNewValue,
        //            int retryCountUntilThrow = RETRY_COUNT_UNTIL_THROW)
        //            where T : class
        //        {
        //#if SUPPORTS_SPIN_WAIT
        //            var spinWait = new SpinWait();
        //#endif
        //            var retryCount = 0;
        //            while (true)
        //            {
        //                var oldValue = value;
        //                var newValue = getNewValue(oldValue, a, b, c, d);
        //                if (Interlocked.CompareExchange(ref value, newValue, oldValue) == oldValue)
        //                    return oldValue;

        //                if (++retryCount > retryCountUntilThrow)
        //                    ThrowRetryCountExceeded(retryCountUntilThrow);

        //#if SUPPORTS_SPIN_WAIT
        //                spinWait.SpinOnce();
        //#endif
        //            }
        //        }
    }

    /// <summary>Printable thing via provided printer </summary>
    public interface IPrintable
    {
        /// <summary>Print to the provided string builder via the provided printer.</summary>
        StringBuilder Print(StringBuilder s, Func<StringBuilder, object, StringBuilder> printer);
    }

    /// <summary>Produces good enough hash codes for the fields</summary>
    public static class Hasher
    {
        /// <summary>Combines hashes of two fields</summary>
        public static int Combine<T1, T2>(T1 a, T2 b) =>
            Combine(a?.GetHashCode() ?? 0, b?.GetHashCode() ?? 0);

        /// <summary>Inspired by System.Tuple.CombineHashCodes</summary>
        public static int Combine(int h1, int h2)
        {
            if (h1 == 0) return h2;
            unchecked
            {
                return (h1 << 5) + h1 ^ h2;
            }
        }
    }

    /// Simple unbounded object pool
    public sealed class StackPool<T> where T : class
    {
        /// <summary>Give me an object</summary>
        [MethodImpl((MethodImplOptions)256)]
        public T RentOrDefault() =>
            Interlocked.Exchange(ref _s, _s?.Tail)?.Head;

        /// <summary>Give it back</summary>
        [MethodImpl((MethodImplOptions)256)]
        public void Return(T x) =>
            Interlocked.Exchange(ref _s, new Stack(x, _s));

        private Stack _s;

        private sealed class Stack
        {
            public readonly T Head;
            public readonly Stack Tail;
            public Stack(T h, Stack t)
            {
                Head = h;
                Tail = t;
            }
        }
    }

    /// <summary>Immutable Key-Value pair. It is reference type (could be check for null), 
    /// which is different from System value type <see cref="KeyValuePair{TKey,TValue}"/>.
    /// In addition provides <see cref="Equals"/> and <see cref="GetHashCode"/> implementations.</summary>
    /// <typeparam name="K">Type of Key.</typeparam><typeparam name="V">Type of Value.</typeparam>
    public class KV<K, V> : IPrintable
    {
        /// <summary>Key.</summary>
        public readonly K Key;

        /// <summary>Value.</summary>
        public readonly V Value;

        /// <summary>Creates Key-Value object by providing key and value. Does Not check either one for null.</summary>
        /// <param name="key">key.</param><param name="value">value.</param>
        public KV(K key, V value)
        {
            Key = key;
            Value = value;
        }

        /// <inheritdoc />
        public StringBuilder Print(StringBuilder s, Func<StringBuilder, object, StringBuilder> printer) =>
            s.Append("(").To(b => Key == null ? b : printer(b, Key))
                .Append(", ").To(b => Value == null ? b : printer(b, Value))
                .Append(')');

        /// <summary>Creates nice string view.</summary><returns>String representation.</returns>
        public override string ToString() =>
            Print(new StringBuilder(), (s, x) => s.Append(x)).ToString();

        /// <summary>Returns true if both key and value are equal to corresponding key-value of other object.</summary>
        public override bool Equals(object obj)
        {
            var other = obj as KV<K, V>;
            return other != null
                   && (ReferenceEquals(other.Key, Key) || Equals(other.Key, Key))
                   && (ReferenceEquals(other.Value, Value) || Equals(other.Value, Value));
        }

        /// <summary>Combines key and value hash code</summary>
        public override int GetHashCode() => Hasher.Combine(Key, Value);
    }

    /// <summary>Helpers for <see cref="KV{K,V}"/>.</summary>
    public static class KV
    {
        /// <summary>Creates the key value pair.</summary>
        public static KV<K, V> Of<K, V>(K key, V value) => new KV<K, V>(key, value);

        /// <summary>Creates the pair with the new value</summary>
        public static KV<K, V> WithValue<K, V>(this KV<K, V> kv, V value) => new KV<K, V>(kv.Key, value);
    }

    /// Simple helper for creation of the pair of two parts.
    public static class KeyValuePair
    {
        /// Pairs key with value.
        public static KeyValuePair<K, V> Pair<K, V>(this K key, V value) => new KeyValuePair<K, V>(key, value);
    }

    /// <summary>Helper structure which allows to distinguish null value from the default value for optional parameter.</summary>
    public struct Opt<T>
    {
        /// <summary>Allows to transparently convert parameter argument to opt structure.</summary>
        public static implicit operator Opt<T>(T value) => new Opt<T>(value);

        /// <summary>Argument value.</summary>
        public readonly T Value;

        /// <summary>Indicates that value is provided.</summary>
        public readonly bool HasValue;

        /// <summary>Wraps passed value in structure. Sets the flag that value is present.</summary>
        public Opt(T value)
        {
            HasValue = true;
            Value = value;
        }

        /// <summary>Helper to get value or default value if value is not present.</summary>
        public T OrDefault(T defaultValue = default) => HasValue ? Value : defaultValue;
    }

    /// <summary>Ever growing list</summary>
    public struct GrowingList<T>
    {
        /// <summary>Default initial capacity </summary>
        public const int DefaultInitialCapacity = 2;

        /// The items array
        public T[] Items;

        /// The count
        public int Count;

        /// Constructs the thing 
        public GrowingList(T[] items, int count = 0)
        {
            Items = items;
            Count = count;
        }

        /// Push the new slot and return the ref to it
        public ref T PushSlot()
        {
            if (Items == null)
                Items = new T[DefaultInitialCapacity];
            else if (Count >= Items.Length)
                Items = Expand(Items);
            return ref Items[Count++];
        }

        /// Adds the new item possibly extending the item collection
        public void PushSlot(T item)
        {
            if (Items == null)
                Items = new T[DefaultInitialCapacity];
            else if (Count >= Items.Length)
                Items = Expand(Items);
            Items[Count++] = item;
        }

        /// Pops the item - just moving the counter back
        public void Pop() => --Count;

        /// Expands the items starting with 2
        private static T[] Expand(T[] items)
        {
            var count = items.Length;
            var newItems = new T[count << 1]; // count x 2
            Array.Copy(items, 0, newItems, 0, count);
            return newItems;
        }

        /// <inheritdoc />
        public override string ToString() =>
            $"Count {Count} of {(Count == 0 || Items == null || Items.Length == 0 ? "empty" : "first (" + Items[0] + ") and last (" + Items[Count - 1] + ")")}";
    }

    /// <summary>Immutable list - simplest linked list with the Head and the Tail.</summary>
    public sealed class ImList<T>
    {
        /// <summary>Empty list to Push to.</summary>
        public static readonly ImList<T> Empty = new ImList<T>();

        /// <summary>True for empty list.</summary>
        public bool IsEmpty => Tail == null;

        /// <summary>First value in a list.</summary>
        public readonly T Head;

        /// <summary>The rest of values or Empty if list has a single value.</summary>
        public readonly ImList<T> Tail;

        /// <summary>Prepends new value and returns new list.</summary>
        public ImList<T> Push(T head) => new ImList<T>(head, this);

        /// <summary>Enumerates the list.</summary>
        public IEnumerable<T> Enumerate()
        {
            if (IsEmpty)
                yield break;
            for (var list = this; !list.IsEmpty; list = list.Tail)
                yield return list.Head;
        }

        /// <summary>String representation for debugging purposes</summary>
        public override string ToString() => IsEmpty
            ? "[]" : Tail.IsEmpty
            ? "[" + Head + "]" : Tail.Tail.IsEmpty
            ? "[" + Head + "," + Tail.Head + "]" : Tail.Tail.Tail.IsEmpty
            ? "[" + Head + "," + Tail.Head + "," + Tail.Tail.Head + "]"
            : "[" + Head + "," + Tail.Head + "," + Tail.Tail.Head + ", ...]";

        private ImList() { }

        private ImList(T head, ImList<T> tail)
        {
            Head = head;
            Tail = tail;
        }
    }

    /// <summary>Extension methods providing basic operations on a list.</summary>
    public static class ImList
    {
        /// Split list into (Head, Tail, IsEmpty) tuple
        public static void Deconstruct<T>(this ImList<T> list, out T head, out ImList<T> tail, out bool isEmpty)
        {
            head = list.Head;
            tail = list.Tail;
            isEmpty = list.IsEmpty;
        }

        /// <summary>
        /// Constructs the reversed list from the parameter array of items
        /// </summary>
        public static ImList<T> List<T>(params T[] items)
        {
            var l = ImList<T>.Empty;
            if (items != null)
                for (var i = items.Length - 1; i >= 0; --i)
                    l = l.Push(items[i]);
            return l;
        }

        /// <summary>
        /// Constructs the list as the reversed input list
        /// </summary>
        public static ImList<T> ToImList<T>(this IList<T> source)
        {
            var l = ImList<T>.Empty;
            if (source != null)
                for (var i = source.Count - 1; i >= 0; --i)
                    l = l.Push(source[i]);
            return l;
        }

        /// <summary>
        /// Constructs the list as the reversed enumerable
        /// </summary>
        public static ImList<T> ToImList<T>(this IEnumerable<T> source)
        {
            if (source is IList<T> list)
                return list.ToImList();
            var l = ImList<T>.Empty;

            if (source != null)
                foreach (var item in source)
                    l = l.Push(item);
            return l.Reverse();
        }

        /// <summary>Constructs list of one element</summary>
        public static ImList<T> List<T>(this T head) => ImList<T>.Empty.Push(head);

        /// <summary>Constructs list from head and tail</summary>
        public static ImList<T> List<T>(this T head, ImList<T> tail) => tail.Push(head);

        /// <summary>Apples some effect action to each element</summary>
        public static void ForEach<T>(this ImList<T> list, Action<T> effect)
        {
            for (; !list.IsEmpty; list = list.Tail)
                effect(list.Head);
        }

        /// <summary>Fold list to a single value. The respective name for it in LINQ is Aggregate</summary>
        public static S Fold<T, S>(this ImList<T> list, S state, Func<T, S, S> reduce)
        {
            if (list.IsEmpty)
                return state;
            var result = state;
            for (; !list.IsEmpty; list = list.Tail)
                result = reduce(list.Head, result);
            return result;
        }

        /// <summary>Fold list to a single value with index of item. The respective name for it in LINQ is Aggregate.</summary>
        public static S Fold<T, S>(this ImList<T> list, S state, Func<T, int, S, S> reduce)
        {
            if (list.IsEmpty)
                return state;
            var result = state;
            for (var i = 0; !list.IsEmpty; list = list.Tail, ++i)
                result = reduce(list.Head, i, result);
            return result;
        }

        /// <summary>Returns new list in reverse order.</summary>
        public static ImList<T> Reverse<T>(this ImList<T> list)
        {
            if (list.IsEmpty || list.Tail.IsEmpty)
                return list;
            var reversed = ImList<T>.Empty;
            for (; !list.IsEmpty; list = list.Tail)
                reversed = reversed.Push(list.Head);
            return reversed;
        }

        /// <summary>Maps the items from the first list to the result list.</summary>
        public static ImList<R> Map<T, R>(this ImList<T> list, Func<T, R> map) =>
            list.Fold(ImList<R>.Empty, (x, r) => List(map(x), r)).Reverse();

        /// <summary>Maps with index</summary>
        public static ImList<R> Map<T, R>(this ImList<T> list, Func<T, int, R> map) =>
            list.Fold(ImList<R>.Empty, (x, i, r) => List(map(x, i), r)).Reverse();

        /// <summary>Copies list to array.</summary>
        public static T[] ToArray<T>(this ImList<T> source) =>
            source.IsEmpty ? ArrayTools.Empty<T>()
            : source.Tail.IsEmpty ? new[] { source.Head } : source.Enumerate().ToArray();
    }

    /// Zipper is an immutable persistent data structure, to represent collection with single focused (selected, active) element.
    /// Consist of REVERSED `Left` immutable list, `Focus` element, and the `Right` immutable list. That's why a Zipper name,
    /// where left and right part are joined / zipped in focus item.
    public sealed class ImZipper<T>
    {
        /// Empty singleton instance to start building your zipper
        public static readonly ImZipper<T> Empty = new ImZipper<T>();

        /// True is zipper does not contain items
        public bool IsEmpty => Count == 0;

        /// Index of Focus item, from `0` to `Count-1`
        public readonly int Index;

        /// Number of items
        public readonly int Count;

        /// Left REVERSED list, so the Head of the list is just prior the Focus item 
        public readonly ImList<T> Left;

        /// Right list, where Head is just after the Focus item
        public readonly ImList<T> Right;

        /// Single focus item
        public readonly T Focus;

        /// <inheritdoc />
        public override string ToString() =>
            IsEmpty ? "[||]" : Count + ":" + Left.Reverse() + "|" + Index + ":" + Focus + "|" + Right;

        /// Sets a new focus and pushes the old focus to the Left list. 
        public ImZipper<T> Append(T focus) => PushLeft(focus);

        /// Sets a new focus and pushes the old focus to the Left list.
        public ImZipper<T> PushLeft(T focus) =>
        IsEmpty ? new ImZipper<T>(ImList<T>.Empty, focus, 0, ImList<T>.Empty, 1)
                : new ImZipper<T>(Left.Push(Focus), focus, Index + 1, Right, Count + 1);

        /// Sets a new focus and pushes the old focus to the right list. 
        public ImZipper<T> Insert(T focus) => PushRight(focus);

        /// Sets a new focus and pushes the old focus to the right list. 
        public ImZipper<T> PushRight(T focus) =>
            IsEmpty ? new ImZipper<T>(ImList<T>.Empty, focus, 0, ImList<T>.Empty, 1)
                : new ImZipper<T>(Left, focus, Index, Right.Push(Focus), Count + 1);

        /// Removes a focus, filling the hole with the item from the left list, or from the right if the left is empty
        public ImZipper<T> PopLeft() =>
            IsEmpty ? this
            : Left.IsEmpty && Right.IsEmpty ? Empty
            : !Left.IsEmpty ? new ImZipper<T>(Left.Tail, Left.Head, Index - 1, Right, Count - 1)
            : new ImZipper<T>(Left, Right.Head, Index, Right.Tail, Count - 1);

        /// Removes a focus, filling the hole with the item from the right list, or from the left if the right is empty
        public ImZipper<T> PopRight() =>
            IsEmpty ? this
            : Left.IsEmpty && Right.IsEmpty ? Empty
            : !Right.IsEmpty ? new ImZipper<T>(Left, Right.Head, Index, Right.Tail, Count - 1)
            : new ImZipper<T>(Left.Tail, Left.Head, Index - 1, Right, Count - 1);

        /// Shifts focus one element to the left (decrementing its Index).
        public ImZipper<T> ShiftLeft() =>
            IsEmpty || Left.IsEmpty ? this
            : new ImZipper<T>(Left.Tail, Left.Head, Index - 1, Right.Push(Focus), Count);

        /// Shifts focus one element to the right (incrementing its Index).
        public ImZipper<T> ShiftRight() =>
            IsEmpty || Right.IsEmpty ? this
            : new ImZipper<T>(Left.Push(Focus), Right.Head, Index + 1, Right.Tail, Count);

        /// Sets a new focus and returns a new zipper with the left and right lists unchanged
        public ImZipper<T> WithFocus(T focus) =>
            IsEmpty ? this : new ImZipper<T>(Left, focus, Index, Right, Count);

        /// Maps over the zipper items producing a new zipper
        public ImZipper<R> Map<R>(Func<T, R> map) =>
            IsEmpty ? ImZipper<R>.Empty
                : new ImZipper<R>(Left.Reverse().Fold(ImList<R>.Empty, (x, r) => r.Push(map(x))),
                    map(Focus), Index, Right.Map(map), Count);

        /// Maps over the zipper items with item index, producing a new zipper
        public ImZipper<R> Map<R>(Func<T, int, R> map) =>
            IsEmpty ? ImZipper<R>.Empty
                : new ImZipper<R>(
                    Left.Reverse().Fold(ImList<R>.Empty, (x, i, r) => r.Push(map(x, i))),
                    map(Focus, Index), Index, Right.Map((x, i) => map(x, Index + 1 + i)), Count);

        private ImZipper() => Index = -1;

        private ImZipper(ImList<T> left, T focus, int index, ImList<T> right, int count)
        {
            Left = left;
            Focus = focus;
            Index = index;
            Right = right;
            Count = count;
        }
    }

    /// Other ImZipper methods
    public static class ImZipper
    {
        /// Appends array items to zipper
        public static ImZipper<T> Zip<T>(params T[] items)
        {
            if (items.IsNullOrEmpty())
                return ImZipper<T>.Empty;
            var z = ImZipper<T>.Empty;
            for (var i = 0; i < items.Length; ++i)
                z = z.PushLeft(items[i]);
            return z;
        }

        /// Converts to array.
        public static T[] ToArray<T>(this ImZipper<T> z)
        {
            if (z.IsEmpty)
                return ArrayTools.Empty<T>();
            var a = new T[z.Count];
            z.Fold(a, (x, i, xs) =>
            {
                xs[i] = x;
                return xs;
            });
            return a;
        }

        /// Shifts focus to a specified index, e.g. a random access
        public static ImZipper<T> ShiftTo<T>(this ImZipper<T> z, int i)
        {
            if (i < 0 || i >= z.Count || i == z.Index)
                return z;
            while (i < z.Index)
                z = z.ShiftLeft();
            while (i > z.Index)
                z = z.ShiftRight();
            return z;
        }

        /// Updates a focus element if it is present, otherwise does nothing.
        /// If the focus item is the equal one, then returns the same zipper back.
        public static ImZipper<T> Update<T>(this ImZipper<T> z, Func<T, T> update)
        {
            if (z.IsEmpty)
                return z;
            var result = update(z.Focus);
            if (ReferenceEquals(z.Focus, result) || result != null && result.Equals(z.Focus))
                return z;
            return z.WithFocus(result);
        }

        /// Update the item at random index, by shifting and updating it
        public static ImZipper<T> UpdateAt<T>(this ImZipper<T> z, int i, Func<T, T> update) =>
            i < 0 || i >= z.Count ? z : z.ShiftTo(i).Update(update);

        /// Update the item at random index, by shifting and updating it
        public static ImZipper<T> RemoveAt<T>(this ImZipper<T> z, int i) =>
            i < 0 || i >= z.Count ? z : z.ShiftTo(i).PopLeft();

        /// Folds zipper to a single value
        public static S Fold<T, S>(this ImZipper<T> z, S state, Func<T, S, S> reduce) =>
            z.IsEmpty ? state :
            z.Right.Fold(reduce(z.Focus, z.Left.Reverse().Fold(state, reduce)), reduce);

        /// Folds zipper to a single value by using an item index
        public static S Fold<T, S>(this ImZipper<T> z, S state, Func<T, int, S, S> reduce)
        {
            if (z.IsEmpty)
                return state;
            var focusIndex = z.Index;
            var reducedLeft = z.Left.Reverse().Fold(state, reduce);
            return z.Right.Fold(reduce(z.Focus, focusIndex, reducedLeft),
                (x, i, r) => reduce(x, focusIndex + i + 1, r));
        }

        /// <summary>Apply some effect action on each element</summary>
        public static void ForEach<T>(this ImZipper<T> z, Action<T> effect)
        {
            if (!z.IsEmpty)
            {
                if (!z.Left.IsEmpty)
                    z.Left.Reverse().ForEach(effect);
                effect(z.Focus);
                if (!z.Right.IsEmpty)
                    z.Right.ForEach(effect);
            }
        }
    }

    /// Given the old value should and the new value should return result updated value.
    public delegate V Update<V>(V oldValue, V newValue);

    /// Update handler including the key
    public delegate V Update<K, V>(K key, V oldValue, V newValue);

    /// <summary>
    /// Fold reducer. Designed as a alternative to `Func{V, S, S}` but with possibility of inlining on the call side.
    /// Note: To get the advantage of inlining the <see cref="Reduce"/> can the interface should be implemented and passed as a NON-GENERIC STRUCT
    /// </summary>
    public interface IFoldReducer<T, S>
    {
        /// <summary>Reduce method</summary>
        S Reduce(T x, S state);
    }

    /// <summary>
    /// Immutable http://en.wikipedia.org/wiki/AVL_tree with integer keys and <typeparamref name="V"/> values.
    /// </summary>
    public class ImMap<V>
    {
        /// <summary>Empty tree to start with.</summary>
        public static readonly ImMap<V> Empty = new ImMap<V>();

        /// <summary>Returns true if tree is empty.</summary>
        public bool IsEmpty => this == Empty;

        /// Prevents multiple creation of an empty tree
        protected ImMap() { }

        /// <summary>Height of the longest sub-tree/branch - 0 for the empty tree</summary>
        public virtual int Height => 0;

        /// <summary>Prints "empty"</summary>
        public override string ToString() => "empty";
    }

    /// <summary>Wraps the stored data with "fixed" reference semantics - when added to the tree it did not change or reconstructed in memory</summary>
    public sealed class ImMapEntry<V> : ImMap<V>
    {
        /// <inheritdoc />
        public override int Height => 1;

        /// The Key is basically the hash, or the Height for ImMapTree
        public readonly int Key;

        /// The value - may be modified if you need a Ref{V} semantics
        public V Value;

        /// <summary>Constructs the entry with the default value</summary>
        public ImMapEntry(int key) => Key = key;

        /// <summary>Constructs the entry</summary>
        public ImMapEntry(int key, V value)
        {
            Key = key;
            Value = value;
        }

        /// Prints the key value pair
        public override string ToString() => Key + ":" + Value;
    }

    /// <summary>
    /// The two level - two node tree with either left or right
    /// </summary>
    public sealed class ImMapBranch<V> : ImMap<V>
    {
        /// <summary>Always two</summary>
        public override int Height => 2;

        /// Contains the once created data node
        public readonly ImMapEntry<V> Entry;

        /// Left sub-tree/branch, or empty.
        public ImMapEntry<V> RightEntry;

        /// Constructor
        public ImMapBranch(ImMapEntry<V> entry, ImMapEntry<V> rightEntry)
        {
            Entry = entry;
            RightEntry = rightEntry;
        }

        /// Prints the key value pair
        public override string ToString() => "h2:" + Entry + "->" + RightEntry;
    }

    /// <summary>
    /// The tree always contains Left and Right node, for the missing leaf we have <see cref="ImMapBranch{V}"/>
    /// </summary>
    public sealed class ImMapTree<V> : ImMap<V>
    {
        /// Starts from 2
        public override int Height => TreeHeight;

        /// Starts from 2 - allows to access the field directly when you know it is a Tree
        public int TreeHeight;

        /// Contains the once created data node
        public readonly ImMapEntry<V> Entry;

        /// Left sub-tree/branch, or empty.
        public ImMap<V> Left;

        /// Right sub-tree/branch, or empty.md
        public ImMap<V> Right;

        internal ImMapTree(ImMapEntry<V> entry, ImMap<V> left, ImMap<V> right, int height)
        {
            Entry = entry;
            Left = left;
            Right = right;
            TreeHeight = height;
        }

        internal ImMapTree(ImMapEntry<V> entry, ImMapEntry<V> leftEntry, ImMapEntry<V> rightEntry)
        {
            Entry = entry;
            Left = leftEntry;
            Right = rightEntry;
            TreeHeight = 2;
        }

        /// <summary>Outputs the brief tree info - mostly for debugging purposes</summary>
        public override string ToString() =>
            "h" + Height + ":" + Entry
                + "->(" + (Left  is ImMapTree<V> leftTree  ? "h" + leftTree.TreeHeight  + ":" + leftTree.Entry  : "" + Left)
                +  ", " + (Right is ImMapTree<V> rightTree ? "h" + rightTree.TreeHeight + ":" + rightTree.Entry : "" + Right)
                + ")";

        /// <summary>Adds or updates the left or right branch</summary>
        public ImMapTree<V> AddOrUpdateLeftOrRightEntry(int key, ImMapEntry<V> entry)
        {
            if (key < Entry.Key)
            {
                var left = Left;
                if (left is ImMapTree<V> leftTree)
                {
                    if (key == leftTree.Entry.Key)
                        return new ImMapTree<V>(Entry,
                            new ImMapTree<V>(entry, leftTree.Left, leftTree.Right, leftTree.TreeHeight),
                            Right, TreeHeight);

                    var newLeftTree = leftTree.AddOrUpdateLeftOrRightEntry(key, entry);
                    return newLeftTree.TreeHeight == leftTree.TreeHeight
                        ? new ImMapTree<V>(Entry, newLeftTree, Right, TreeHeight)
                        : BalanceNewLeftTree(newLeftTree);
                }

                if (left is ImMapBranch<V> leftBranch)
                {
                    if (key < leftBranch.Entry.Key)
                        return new ImMapTree<V>(Entry,
                            new ImMapTree<V>(leftBranch.Entry, entry, leftBranch.RightEntry),
                            Right, TreeHeight);

                    if (key > leftBranch.Entry.Key)
                    {
                        var newLeft =
                            //            5                         5
                            //       2        ?  =>             3        ?
                            //         3                      2   4
                            //          4
                            key > leftBranch.RightEntry.Key ? new ImMapTree<V>(leftBranch.RightEntry, leftBranch.Entry, entry)
                            //            5                         5
                            //      2          ?  =>            2.5        ?
                            //          3                      2   3
                            //       2.5  
                            : key < leftBranch.RightEntry.Key ? new ImMapTree<V>(entry, leftBranch.Entry, leftBranch.RightEntry)
                            : (ImMap<V>)new ImMapBranch<V>(leftBranch.Entry, entry);

                        return new ImMapTree<V>(Entry, newLeft, Right, TreeHeight);
                    }

                    return new ImMapTree<V>(Entry,
                        new ImMapBranch<V>(entry, leftBranch.RightEntry), Right, TreeHeight);
                }

                var leftLeaf = (ImMapEntry<V>)left;
                return key > leftLeaf.Key ? new ImMapTree<V>(Entry, new ImMapBranch<V>(leftLeaf, entry), Right, 3)
                    : key < leftLeaf.Key ? new ImMapTree<V>(Entry, new ImMapBranch<V>(entry, leftLeaf), Right, 3)
                    : new ImMapTree<V>(Entry, entry, Right, TreeHeight);
            }
            else
            {
                var right = Right;
                if (right is ImMapTree<V> rightTree)
                {
                    if (key == rightTree.Entry.Key)
                        return new ImMapTree<V>(Entry, Left,
                            new ImMapTree<V>(entry, rightTree.Left, rightTree.Right, rightTree.TreeHeight),
                            TreeHeight);

                    var newRightTree = rightTree.AddOrUpdateLeftOrRightEntry(key, entry);
                    return newRightTree.TreeHeight == rightTree.TreeHeight
                        ? new ImMapTree<V>(Entry, Left, newRightTree, TreeHeight)
                        : BalanceNewRightTree(newRightTree);
                }

                if (right is ImMapBranch<V> rightBranch)
                {
                    if (key > rightBranch.Entry.Key)
                    {
                        var newRight =
                            //      5                5      
                            //  ?       6    =>  ?       8  
                            //            8            6   !
                            //              !               
                            key > rightBranch.RightEntry.Key ? new ImMapTree<V>(rightBranch.RightEntry, rightBranch.Entry, entry)
                            //      5                 5      
                            //  ?       6     =>  ?       7  
                            //              8            6  8
                            //            7               
                            : key < rightBranch.RightEntry.Key ? new ImMapTree<V>(entry, rightBranch.Entry, rightBranch.RightEntry)
                            : (ImMap<V>)new ImMapBranch<V>(rightBranch.Entry, entry);

                        return new ImMapTree<V>(Entry, Left, newRight, TreeHeight);
                    }

                    if (key < rightBranch.Entry.Key)
                        return new ImMapTree<V>(Entry, Left,
                            new ImMapTree<V>(rightBranch.Entry, entry, rightBranch.RightEntry),
                            TreeHeight);

                    return new ImMapTree<V>(Entry, Left, new ImMapBranch<V>(entry, rightBranch.RightEntry), TreeHeight);
                }

                var rightLeaf = (ImMapEntry<V>)right;
                return key > rightLeaf.Key
                        ? new ImMapTree<V>(Entry, Left, new ImMapBranch<V>(rightLeaf, entry), 3)
                    : key < rightLeaf.Key
                        ? new ImMapTree<V>(Entry, Left, new ImMapBranch<V>(entry, rightLeaf), 3)
                        : new ImMapTree<V>(Entry, Left, entry, TreeHeight);
            }
        }

        /// <summary>Adds the left or right branch</summary>
        public ImMapTree<V> AddUnsafeLeftOrRightEntry(int key, ImMapEntry<V> entry)
        {
            if (key < Entry.Key)
            {
                var left = Left;
                if (left is ImMapTree<V> leftTree)
                {
                    var newLeftTree = leftTree.AddUnsafeLeftOrRightEntry(key, entry);
                    return newLeftTree.TreeHeight == leftTree.TreeHeight
                        ? new ImMapTree<V>(Entry, newLeftTree, Right, TreeHeight)
                        : BalanceNewLeftTree(newLeftTree);
                }

                if (left is ImMapBranch<V> leftBranch)
                {
                    if (key < leftBranch.Entry.Key)
                        return new ImMapTree<V>(Entry,
                            new ImMapTree<V>(leftBranch.Entry, entry, leftBranch.RightEntry),
                            Right, TreeHeight);

                    var newLeft = key > leftBranch.RightEntry.Key
                            ? new ImMapTree<V>(leftBranch.RightEntry, leftBranch.Entry, entry)
                            : new ImMapTree<V>(entry, leftBranch.Entry, leftBranch.RightEntry);

                    return new ImMapTree<V>(Entry, newLeft, Right, TreeHeight);
                }

                var leftLeaf = (ImMapEntry<V>)left;
                return key > leftLeaf.Key
                    ? new ImMapTree<V>(Entry, new ImMapBranch<V>(leftLeaf, entry), Right, 3)
                    : new ImMapTree<V>(Entry, new ImMapBranch<V>(entry, leftLeaf), Right, 3);
            }
            else
            {
                var right = Right;
                if (right is ImMapTree<V> rightTree)
                {
                    var newRightTree = rightTree.AddUnsafeLeftOrRightEntry(key, entry);
                    return newRightTree.TreeHeight == rightTree.TreeHeight
                        ? new ImMapTree<V>(Entry, Left, newRightTree, TreeHeight)
                        : BalanceNewRightTree(newRightTree);
                }

                if (right is ImMapBranch<V> rightBranch)
                {
                    if (key > rightBranch.Entry.Key)
                    {
                        var newRight = key > rightBranch.RightEntry.Key
                            ? new ImMapTree<V>(rightBranch.RightEntry, rightBranch.Entry, entry)
                            : new ImMapTree<V>(entry, rightBranch.Entry, rightBranch.RightEntry);

                        return new ImMapTree<V>(Entry, Left, newRight, TreeHeight);
                    }

                    return new ImMapTree<V>(Entry, Left,
                        new ImMapTree<V>(rightBranch.Entry, entry, rightBranch.RightEntry),
                        TreeHeight);
                }

                var rightLeaf = (ImMapEntry<V>)right;
                return key > rightLeaf.Key
                    ? new ImMapTree<V>(Entry, Left, new ImMapBranch<V>(rightLeaf, entry), 3)
                    : new ImMapTree<V>(Entry, Left, new ImMapBranch<V>(entry, rightLeaf), 3);
            }
        }

        /// <summary>Adds to the left or right branch, or keeps the un-modified map</summary>
        public ImMapTree<V> AddOrKeepLeftOrRight(int key, V value)
        {
            if (key < Entry.Key)
            {
                var left = Left;
                if (left is ImMapTree<V> leftTree)
                {
                    if (key == leftTree.Entry.Key)
                        return this;

                    var newLeftTree = leftTree.AddOrKeepLeftOrRight(key, value);
                    return newLeftTree == leftTree ? this
                        : newLeftTree.TreeHeight == leftTree.TreeHeight
                            ? new ImMapTree<V>(Entry, newLeftTree, Right, TreeHeight)
                            : BalanceNewLeftTree(newLeftTree);
                }

                if (left is ImMapBranch<V> leftBranch)
                {
                    if (key < leftBranch.Entry.Key)
                        return new ImMapTree<V>(Entry, 
                            new ImMapTree<V>(leftBranch.Entry, new ImMapEntry<V>(key, value), leftBranch.RightEntry),
                            Right, TreeHeight);
                    if (key > leftBranch.RightEntry.Key)
                        return new ImMapTree<V>(Entry,
                            new ImMapTree<V>(leftBranch.RightEntry, leftBranch.Entry, new ImMapEntry<V>(key, value)),
                            Right, TreeHeight);
                    if (key > leftBranch.Entry.Key && key < leftBranch.RightEntry.Key)
                        return new ImMapTree<V>(Entry,
                            new ImMapTree<V>(new ImMapEntry<V>(key, value), leftBranch.Entry, leftBranch.RightEntry),
                            Right, TreeHeight);
                    return this;
                }

                var leftLeaf = (ImMapEntry<V>)left;
                return key > leftLeaf.Key
                        ? new ImMapTree<V>(Entry, new ImMapBranch<V>(leftLeaf, new ImMapEntry<V>(key, value)), Right, 3)
                    : key < leftLeaf.Key
                        ? new ImMapTree<V>(Entry, new ImMapBranch<V>(new ImMapEntry<V>(key, value), leftLeaf), Right, 3)
                    : this;
            }
            else
            {
                var right = Right;
                if (right is ImMapTree<V> rightTree)
                {
                    if (key == rightTree.Entry.Key)
                        return this;

                    var newRightTree = rightTree.AddOrKeepLeftOrRight(key, value);
                    return newRightTree == rightTree ? this
                        : newRightTree.TreeHeight == rightTree.TreeHeight
                            ? new ImMapTree<V>(Entry, Left, newRightTree, TreeHeight)
                            : BalanceNewRightTree(newRightTree);
                }

                if (right is ImMapBranch<V> rightBranch)
                {
                    if (key > rightBranch.RightEntry.Key)
                        return new ImMapTree<V>(Entry, Left,
                            new ImMapTree<V>(rightBranch.RightEntry, rightBranch.Entry, new ImMapEntry<V>(key, value)),
                            TreeHeight);
                    if (key < rightBranch.Entry.Key)
                        return new ImMapTree<V>(Entry, Left,
                            new ImMapTree<V>(rightBranch.Entry, new ImMapEntry<V>(key, value), rightBranch.RightEntry),
                            TreeHeight);
                    if (key > rightBranch.Entry.Key && key < rightBranch.RightEntry.Key)
                        return new ImMapTree<V>(Entry, Left,
                            new ImMapTree<V>(new ImMapEntry<V>(key, value), rightBranch.Entry, rightBranch.RightEntry),
                            TreeHeight);
                    return this;
                }

                var rightLeaf = (ImMapEntry<V>)right;
                return key > rightLeaf.Key
                    ? new ImMapTree<V>(Entry, Left, new ImMapBranch<V>(rightLeaf, new ImMapEntry<V>(key, value)), 3)
                    : key < rightLeaf.Key
                        ? new ImMapTree<V>(Entry, Left, new ImMapBranch<V>(new ImMapEntry<V>(key, value), rightLeaf), 3)
                    : this;
            }
        }

        /// <summary>Adds to the left or right branch, or keeps the un-modified map</summary>
        public ImMapTree<V> AddOrKeepLeftOrRight(int key)
        {
            if (key < Entry.Key)
            {
                var left = Left;
                if (left is ImMapTree<V> leftTree)
                {
                    if (key == leftTree.Entry.Key)
                        return this;

                    var newLeftTree = leftTree.AddOrKeepLeftOrRight(key);
                    return newLeftTree == leftTree ? this
                        : newLeftTree.TreeHeight == leftTree.TreeHeight
                            ? new ImMapTree<V>(Entry, newLeftTree, Right, TreeHeight)
                            : BalanceNewLeftTree(newLeftTree);
                }

                if (left is ImMapBranch<V> leftBranch)
                {
                    if (key < leftBranch.Entry.Key)
                        return new ImMapTree<V>(Entry,
                            new ImMapTree<V>(leftBranch.Entry, new ImMapEntry<V>(key), leftBranch.RightEntry),
                            Right, TreeHeight);
                    if (key > leftBranch.RightEntry.Key)
                        return new ImMapTree<V>(Entry,
                            new ImMapTree<V>(leftBranch.RightEntry, leftBranch.Entry, new ImMapEntry<V>(key)),
                            Right, TreeHeight);
                    if (key > leftBranch.Entry.Key && key < leftBranch.RightEntry.Key)
                        return new ImMapTree<V>(Entry,
                            new ImMapTree<V>(new ImMapEntry<V>(key), leftBranch.Entry, leftBranch.RightEntry),
                            Right, TreeHeight);
                    return this;
                }

                var leftLeaf = (ImMapEntry<V>)left;
                return key > leftLeaf.Key
                        ? new ImMapTree<V>(Entry, new ImMapBranch<V>(leftLeaf, new ImMapEntry<V>(key)), Right, 3)
                    : key < leftLeaf.Key
                        ? new ImMapTree<V>(Entry, new ImMapBranch<V>(new ImMapEntry<V>(key), leftLeaf), Right, 3)
                    : this;
            }
            else
            {
                var right = Right;
                if (right is ImMapTree<V> rightTree)
                {
                    if (key == rightTree.Entry.Key)
                        return this;

                    // note: tree always contains left and right (for the missing leaf we have a Branch)
                    var newRightTree = rightTree.AddOrKeepLeftOrRight(key);
                    return newRightTree == rightTree ? this
                        : newRightTree.TreeHeight == rightTree.TreeHeight
                            ? new ImMapTree<V>(Entry, Left, newRightTree, TreeHeight)
                            : BalanceNewRightTree(newRightTree);
                }

                if (right is ImMapBranch<V> rightBranch)
                {
                    if (key > rightBranch.RightEntry.Key)
                        return new ImMapTree<V>(Entry, Left,
                            new ImMapTree<V>(rightBranch.RightEntry, rightBranch.Entry, new ImMapEntry<V>(key)),
                            TreeHeight);
                    if (key < rightBranch.Entry.Key)
                        return new ImMapTree<V>(Entry, Left,
                            new ImMapTree<V>(rightBranch.Entry, new ImMapEntry<V>(key), rightBranch.RightEntry),
                            TreeHeight);
                    if (key > rightBranch.Entry.Key && key < rightBranch.RightEntry.Key)
                        return new ImMapTree<V>(Entry, Left,
                            new ImMapTree<V>(new ImMapEntry<V>(key), rightBranch.Entry, rightBranch.RightEntry),
                            TreeHeight);
                    return this;
                }

                var rightLeaf = (ImMapEntry<V>)right;
                return key > rightLeaf.Key
                        ? new ImMapTree<V>(Entry, Left, new ImMapBranch<V>(rightLeaf, new ImMapEntry<V>(key)), 3)
                    : key < rightLeaf.Key 
                        ? new ImMapTree<V>(Entry, Left, new ImMapBranch<V>(new ImMapEntry<V>(key), rightLeaf), 3)
                    : this;
            }
        }

        /// <summary>Adds to the left or right branch, or keeps the un-modified map</summary>
        public ImMapTree<V> AddOrKeepLeftOrRightEntry(int key, ImMapEntry<V> entry)
        {
            if (key < Entry.Key)
            {
                var left = Left;
                if (left is ImMapTree<V> leftTree)
                {
                    if (key == leftTree.Entry.Key)
                        return this;

                    var newLeftTree = leftTree.AddOrKeepLeftOrRightEntry(key, entry);
                    return newLeftTree == leftTree ? this
                        : newLeftTree.TreeHeight == leftTree.TreeHeight
                            ? new ImMapTree<V>(Entry, newLeftTree, Right, TreeHeight)
                            : BalanceNewLeftTree(newLeftTree);
                }

                if (left is ImMapBranch<V> leftBranch)
                {
                    if (key < leftBranch.Entry.Key)
                        return new ImMapTree<V>(Entry,
                            new ImMapTree<V>(leftBranch.Entry, entry, leftBranch.RightEntry),
                            Right, TreeHeight);
                    if (key > leftBranch.RightEntry.Key)
                        return new ImMapTree<V>(Entry,
                            new ImMapTree<V>(leftBranch.RightEntry, leftBranch.Entry, entry),
                            Right, TreeHeight);
                    if (key > leftBranch.Entry.Key && key < leftBranch.RightEntry.Key)
                        return new ImMapTree<V>(Entry,
                            new ImMapTree<V>(entry, leftBranch.Entry, leftBranch.RightEntry),
                            Right, TreeHeight);
                    return this;
                }

                var leftLeaf = (ImMapEntry<V>)left;
                return key > leftLeaf.Key
                        ? new ImMapTree<V>(Entry, new ImMapBranch<V>(leftLeaf, entry), Right, 3)
                    : key < leftLeaf.Key
                        ? new ImMapTree<V>(Entry, new ImMapBranch<V>(entry, leftLeaf), Right, 3)
                    : this;
            }
            else
            {
                var right = Right;
                if (right is ImMapTree<V> rightTree)
                {
                    if (key == rightTree.Entry.Key)
                        return this;

                    // note: tree always contains left and right (for the missing leaf we have a Branch)
                    var newRightTree = rightTree.AddOrKeepLeftOrRightEntry(key, entry);
                    return newRightTree == rightTree ? this
                        : newRightTree.TreeHeight == rightTree.TreeHeight
                            ? new ImMapTree<V>(Entry, Left, newRightTree, TreeHeight)
                            : BalanceNewRightTree(newRightTree);
                }

                if (right is ImMapBranch<V> rightBranch)
                {
                    if (key > rightBranch.RightEntry.Key)
                        return new ImMapTree<V>(Entry, Left,
                            new ImMapTree<V>(rightBranch.RightEntry, rightBranch.Entry, entry),
                            TreeHeight);
                    if (key < rightBranch.Entry.Key)
                        return new ImMapTree<V>(Entry, Left,
                            new ImMapTree<V>(rightBranch.Entry, entry, rightBranch.RightEntry),
                            TreeHeight);
                    if (key > rightBranch.Entry.Key && key < rightBranch.RightEntry.Key)
                        return new ImMapTree<V>(Entry, Left,
                            new ImMapTree<V>(entry, rightBranch.Entry, rightBranch.RightEntry),
                            TreeHeight);
                    return this;
                }

                var rightLeaf = (ImMapEntry<V>)right;
                return key > rightLeaf.Key
                    ? new ImMapTree<V>(Entry, Left, new ImMapBranch<V>(rightLeaf, entry), 3)
                    : key < rightLeaf.Key
                        ? new ImMapTree<V>(Entry, Left, new ImMapBranch<V>(entry, rightLeaf), 3)
                    : this;
            }
        }

        private ImMapTree<V> BalanceNewLeftTree(ImMapTree<V> newLeftTree)
        {
            var rightHeight = Right.Height;
            var delta = newLeftTree.TreeHeight - rightHeight;
            if (delta <= 0)
                return new ImMapTree<V>(Entry, newLeftTree, Right, TreeHeight);

            if (delta == 1)
                return new ImMapTree<V>(Entry, newLeftTree, Right, newLeftTree.TreeHeight + 1);

            // here is the balancing art comes into place
            if (rightHeight == 1)
            {
                if (newLeftTree.Left is ImMapEntry<V> leftLeftLeaf)
                {
                    //            30                    15
                    //    10            40 =>    10           20
                    //  5     15               5    12     30    40
                    //     12   20                     
                    if (newLeftTree.Right is ImMapTree<V> leftRightTree)
                    {
                        newLeftTree.Right = leftRightTree.Left;
                        newLeftTree.TreeHeight = 2;
                        return new ImMapTree<V>(leftRightTree.Entry,
                            newLeftTree,
                            new ImMapTree<V>(Entry, leftRightTree.Right, Right, 2),
                            3);
                    }

                    // we cannot reuse the new left tree here because it is reduced into the branch
                    //           30                     15
                    //    10            40 =>    5           20
                    //  5    15                    10     30    40
                    //         20                     
                    var leftRightBranch = (ImMapBranch<V>)newLeftTree.Right;
                    return new ImMapTree<V>(leftRightBranch.Entry,
                        new ImMapBranch<V>(leftLeftLeaf, newLeftTree.Entry),
                        new ImMapTree<V>(Entry, leftRightBranch.RightEntry, Right, 2),
                        3);
                }

                newLeftTree.Right = new ImMapTree<V>(Entry, newLeftTree.Right, Right, 2);
                newLeftTree.TreeHeight = 3;
                return newLeftTree;
            }

            var leftLeftHeight = (newLeftTree.Left as ImMapTree<V>)?.TreeHeight ?? 2;
            var leftRightHeight = (newLeftTree.Right as ImMapTree<V>)?.TreeHeight ?? 2;
            if (leftLeftHeight < leftRightHeight)
            {
                var leftRightTree = (ImMapTree<V>)newLeftTree.Right;

                newLeftTree.Right = leftRightTree.Left;
                newLeftTree.TreeHeight = leftLeftHeight + 1;
                return new ImMapTree<V>(leftRightTree.Entry,
                    newLeftTree,
                    new ImMapTree<V>(Entry, leftRightTree.Right, Right, rightHeight + 1),
                    leftLeftHeight + 2);

                //return new ImMapTree<V>(leftRightTree.Entry,
                //    new ImMapTree<V>(newLeftTree.Entry, leftLeftHeight, newLeftTree.Left, leftRightTree.Left),
                //    new ImMapTree<V>(Entry, leftRightTree.Right, rightHeight, Right));
            }

            newLeftTree.Right = new ImMapTree<V>(Entry, newLeftTree.Right, Right, leftRightHeight + 1);
            newLeftTree.TreeHeight = leftRightHeight + 2;
            return newLeftTree;
        }

        private ImMapTree<V> BalanceNewRightTree(ImMapTree<V> newRightTree)
        {
            var leftHeight = Left.Height;
            var delta = newRightTree.Height - leftHeight;
            if (delta <= 0)
                return new ImMapTree<V>(Entry, Left, newRightTree, TreeHeight);
            if (delta == 1)
                return new ImMapTree<V>(Entry, Left, newRightTree, newRightTree.TreeHeight + 1);

            if (leftHeight == 1)
            {
                // here we need to re-balance by default, because the new right tree is at least 3 level (actually exactly 3 or it would be too unbalanced)
                // double rotation needed if only the right-right is a leaf
                if (newRightTree.Right is ImMapEntry<V> == false)
                {
                    newRightTree.Left = new ImMapTree<V>(Entry, Left, newRightTree.Left, 2);
                    newRightTree.TreeHeight = 3;
                    return newRightTree;
                }

                //        20                        30       
                // 10             40     =>    20        40  
                //            30      50     10  25    35  50
                //          25  35                           
                if (newRightTree.Left is ImMapTree<V> rightLeftTree)
                {
                    newRightTree.Left = rightLeftTree.Right;
                    newRightTree.TreeHeight = 2;
                    return new ImMapTree<V>(rightLeftTree.Entry,
                        new ImMapTree<V>(Entry, Left, rightLeftTree.Left, 2),
                        newRightTree, 3);
                }

                //        20                        30       
                // 10             40     =>    10        40  
                //            30      50         20    35  50
                //              35                           
                var rightLeftBranch = (ImMapBranch<V>)newRightTree.Left;
                newRightTree.Left = rightLeftBranch.RightEntry;
                newRightTree.TreeHeight = 2;
                return new ImMapTree<V>(rightLeftBranch.Entry,
                    new ImMapBranch<V>((ImMapEntry<V>)Left, Entry),
                    newRightTree, 3);
            }

            var rightRightHeight = (newRightTree.Right as ImMapTree<V>)?.TreeHeight ?? 2;
            var rightLeftHeight = (newRightTree.Left as ImMapTree<V>)?.TreeHeight ?? 2;
            if (rightRightHeight < rightLeftHeight)
            {
                var rightLeftTree = (ImMapTree<V>)newRightTree.Left;
                newRightTree.Left = rightLeftTree.Right;
                // the height now should be defined by rr - because left now is shorter by 1
                newRightTree.TreeHeight = rightRightHeight + 1;
                // the whole height consequentially can be defined by `newRightTree` (rr+1) because left is consist of short Left and -2 rl.Left
                return new ImMapTree<V>(rightLeftTree.Entry,
                    // Left should be >= rightLeft.Left because it maybe rightLeft.Right which defines rl height
                    new ImMapTree<V>(Entry, Left, rightLeftTree.Left, leftHeight + 1),
                    newRightTree,
                    rightRightHeight + 2);

                //return new ImMapTree<V>(rightLeftTree.Entry,
                //    new ImMapTree<V>(Entry, leftHeight, Left, rightLeftTree.Left),
                //    new ImMapTree<V>(newRightTree.Entry, rightLeftTree.Right, rightRightHeight, newRightTree.Right));
            }

            Debug.Assert(rightLeftHeight >= leftHeight, "The whole rightHeight > leftHeight by 2, and rightRight >= leftHeight but not more than by 2");

            // we may decide on the height because the Left smaller by 2
            newRightTree.Left = new ImMapTree<V>(Entry, Left, newRightTree.Left, rightLeftHeight + 1);
            // if rr was > rl by 1 than new rl+1 should be equal height to rr now, if rr was == rl than new rl wins anyway
            newRightTree.TreeHeight = rightLeftHeight + 2;
            return newRightTree;
        }
    }

    /// <summary>ImMap methods</summary>
    public static class ImMap
    {
        /// <summary> Adds or updates the value by key in the map, always returns a modified map </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMap<V> AddOrUpdateEntry<V>(this ImMap<V> map, ImMapEntry<V> entry)
        {
            if (map == ImMap<V>.Empty)
                return entry;

            var key = entry.Key;
            if (map is ImMapEntry<V> leaf)
                return key > leaf.Key ? new ImMapBranch<V>(leaf, entry)
                    : key < leaf.Key ? new ImMapBranch<V>(entry, leaf)
                    : (ImMap<V>)entry;

            if (map is ImMapBranch<V> branch)
            {
                if (key > branch.Entry.Key)
                    //   5                  10
                    //        10     =>  5     11
                    //           11           
                    return key > branch.RightEntry.Key
                        ? new ImMapTree<V>(branch.RightEntry, branch.Entry, entry)
                        //   5               7
                        //        10  =>  5     10
                        //      7           
                        : key < branch.RightEntry.Key // rotate if right
                            ? new ImMapTree<V>(entry, branch.Entry, branch.RightEntry)
                            : (ImMap<V>)new ImMapBranch<V>(branch.Entry, entry);

                return key < branch.Entry.Key
                    ? new ImMapTree<V>(branch.Entry, entry, branch.RightEntry)
                    : (ImMap<V>)new ImMapBranch<V>(entry, branch.RightEntry);
            }

            var tree = (ImMapTree<V>)map;
            return key == tree.Entry.Key
                ? new ImMapTree<V>(entry, tree.Left, tree.Right, tree.TreeHeight)
                : tree.AddOrUpdateLeftOrRightEntry(key, entry);
        }

        /// <summary> Adds or updates the value by key in the map, always returns a modified map </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMap<V> AddOrUpdate<V>(this ImMap<V> map, int key, V value) =>
            map.AddOrUpdateEntry(new ImMapEntry<V>(key, value));

        /// <summary> Adds the value by key in the map - ASSUMES that the key is not in the map, always returns a modified map </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMap<V> AddEntryUnsafe<V>(this ImMap<V> map, ImMapEntry<V> entry)
        {
            if (map == ImMap<V>.Empty)
                return entry;

            var key = entry.Key;
            if (map is ImMapEntry<V> leaf)
                return key > leaf.Key
                    ? new ImMapBranch<V>(leaf, entry)
                    : new ImMapBranch<V>(entry, leaf);

            if (map is ImMapBranch<V> branch)
                return key < branch.Entry.Key 
                        ? new ImMapTree<V>(branch.Entry, entry, branch.RightEntry)
                    : key > branch.RightEntry.Key 
                        ? new ImMapTree<V>(branch.RightEntry, branch.Entry, entry)
                        : new ImMapTree<V>(entry, branch.Entry, branch.RightEntry);

            return ((ImMapTree<V>)map).AddUnsafeLeftOrRightEntry(key, entry);
        }

        /// <summary> Adds the value for the key or returns the un-modified map if key is already present </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMap<V> AddOrKeep<V>(this ImMap<V> map, int key, V value)
        {
            if (map == ImMap<V>.Empty)
                return new ImMapEntry<V>(key, value);

            if (map is ImMapEntry<V> leaf)
                return key > leaf.Key ? new ImMapBranch<V>(leaf, new ImMapEntry<V>(key, value))
                    : key < leaf.Key ? new ImMapBranch<V>(new ImMapEntry<V>(key, value), leaf)
                    : map;

            if (map is ImMapBranch<V> branch)
                return key < branch.Entry.Key
                        ? new ImMapTree<V>(branch.Entry, new ImMapEntry<V>(key, value), branch.RightEntry)
                     : key > branch.RightEntry.Key
                        ? new ImMapTree<V>(branch.RightEntry, branch.Entry, new ImMapEntry<V>(key, value))
                     : key > branch.Entry.Key && key < branch.RightEntry.Key
                        ? new ImMapTree<V>(new ImMapEntry<V>(key, value), branch.Entry, branch.RightEntry)
                     : map;

            var tree = (ImMapTree<V>)map;
            return key != tree.Entry.Key
                ? tree.AddOrKeepLeftOrRight(key, value)
                : map;
        }

        /// <summary> Adds the entry with default value for the key or returns the un-modified map if key is already present </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMap<V> AddOrKeep<V>(this ImMap<V> map, int key)
        {
            if (map == ImMap<V>.Empty)
                return new ImMapEntry<V>(key);

            if (map is ImMapEntry<V> leaf)
                return key > leaf.Key ? new ImMapBranch<V>(leaf, new ImMapEntry<V>(key))
                    : key < leaf.Key ? new ImMapBranch<V>(new ImMapEntry<V>(key), leaf)
                    : map;

            if (map is ImMapBranch<V> branch)
                return key < branch.Entry.Key
                        ? new ImMapTree<V>(branch.Entry, new ImMapEntry<V>(key), branch.RightEntry)
                     : key > branch.RightEntry.Key
                        ? new ImMapTree<V>(branch.RightEntry, branch.Entry, new ImMapEntry<V>(key))
                     : key > branch.Entry.Key && key < branch.RightEntry.Key
                        ? new ImMapTree<V>(new ImMapEntry<V>(key), branch.Entry, branch.RightEntry)
                     : map;

            var tree = (ImMapTree<V>)map;
            return key != tree.Entry.Key ? tree.AddOrKeepLeftOrRight(key) : map;
        }

        /// <summary> Adds the entry for the key or returns the un-modified map if key is already present </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMap<V> AddOrKeepEntry<V>(this ImMap<V> map, ImMapEntry<V> entry)
        {
            if (map == ImMap<V>.Empty)
                return entry;

            var key = entry.Key;
            if (map is ImMapEntry<V> leaf)
                return key > leaf.Key ? new ImMapBranch<V>(leaf, entry)
                    : key < leaf.Key ? new ImMapBranch<V>(entry, leaf)
                    : map;

            if (map is ImMapBranch<V> branch)
                return key < branch.Entry.Key 
                        ? new ImMapTree<V>(branch.Entry, entry, branch.RightEntry)
                     : key > branch.RightEntry.Key 
                         ? new ImMapTree<V>(branch.RightEntry, branch.Entry, entry)
                     : key > branch.Entry.Key && key < branch.RightEntry.Key
                        ? new ImMapTree<V>(entry, branch.Entry, branch.RightEntry)
                     : map;

            var tree = (ImMapTree<V>)map;
            return key != tree.Entry.Key ? tree.AddOrKeepLeftOrRightEntry(key, entry) : map;
        }

        ///<summary>Returns the new map with the updated value for the key, or the same map if the key was not found.</summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMap<V> Update<V>(this ImMap<V> map, int key, V value) =>
            map.Contains(key) ? map.UpdateImpl(key, new ImMapEntry<V>(key, value)) : map;

        ///<summary>Returns the new map with the updated value for the key, ASSUMES that the key is not in the map.</summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMap<V> UpdateEntryUnsafe<V>(this ImMap<V> map, ImMapEntry<V> entry) =>
            map.UpdateImpl(entry.Key, entry);

        internal static ImMap<V> UpdateImpl<V>(this ImMap<V> map, int key, ImMapEntry<V> entry)
        {
            if (map is ImMapTree<V> tree)
                return key > tree.Entry.Key ? new ImMapTree<V>(tree.Entry, tree.Left, tree.Right.UpdateImpl(key, entry), tree.TreeHeight)
                    : key < tree.Entry.Key ? new ImMapTree<V>(tree.Entry, tree.Left.UpdateImpl(key, entry), tree.Right, tree.TreeHeight)
                    : new ImMapTree<V>(entry, tree.Left, tree.Right, tree.TreeHeight);

            // the key was found - so it should be either entry or right entry
            if (map is ImMapBranch<V> branch)
                return key == branch.Entry.Key
                    ? new ImMapBranch<V>(entry, branch.RightEntry)
                    : new ImMapBranch<V>(branch.Entry, entry);

            return entry;
        }

        ///<summary>Returns the new map with the value set to default, or the same map if the key was not found.</summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMap<V> UpdateToDefault<V>(this ImMap<V> map, int key) =>
            map.Contains(key) ? map.UpdateToDefaultImpl(key) : map;

        internal static ImMap<V> UpdateToDefaultImpl<V>(this ImMap<V> map, int key)
        {
            if (map is ImMapTree<V> tree)
                return key > tree.Entry.Key
                    ? new ImMapTree<V>(tree.Entry, tree.Left, tree.Right.UpdateToDefaultImpl(key), tree.TreeHeight)
                    : key < tree.Entry.Key
                        ? new ImMapTree<V>(tree.Entry, tree.Left.UpdateToDefaultImpl(key), tree.Right, tree.TreeHeight)
                        : new ImMapTree<V>(new ImMapEntry<V>(key), tree.Left, tree.Right, tree.TreeHeight);

            // the key was found - so it should be either entry or right entry
            if (map is ImMapBranch<V> branch)
                return key == branch.Entry.Key
                    ? new ImMapBranch<V>(new ImMapEntry<V>(key), branch.RightEntry)
                    : new ImMapBranch<V>(branch.Entry, new ImMapEntry<V>(key));

            return new ImMapEntry<V>(key);
        }

        /// <summary> Returns `true` if key is found or `false` otherwise. </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static bool Contains<V>(this ImMap<V> map, int key)
        {
            ImMapEntry<V> entry;
            while (map is ImMapTree<V> tree)
            {
                entry = tree.Entry;
                if (key > entry.Key)
                    map = tree.Right;
                else if (key < entry.Key)
                    map = tree.Left;
                else
                    return true;
            }

            if (map is ImMapBranch<V> branch)
                return branch.Entry.Key == key || branch.RightEntry.Key == key;

            entry = map as ImMapEntry<V>;
            return entry != null && entry.Key == key;
        }

        /// <summary> Returns the entry if key is found or null otherwise. </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMapEntry<V> GetEntryOrDefault<V>(this ImMap<V> map, int key)
        {
            ImMapEntry<V> entry;
            while (map is ImMapTree<V> tree)
            {
                entry = tree.Entry;
                if (key > entry.Key)
                    map = tree.Right;
                else if (key < entry.Key)
                    map = tree.Left;
                else
                    return entry;
            }

            if (map is ImMapBranch<V> branch)
                return branch.Entry.Key == key ? branch.Entry
                    : branch.RightEntry.Key == key ? branch.RightEntry
                    : null;

            entry = map as ImMapEntry<V>;
            return entry != null && entry.Key == key ? entry : null;
        }

        /// <summary> Returns the value if key is found or default value otherwise. </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static V GetValueOrDefault<V>(this ImMap<V> map, int key)
        {
            ImMapEntry<V> entry;
            while (map is ImMapTree<V> tree)
            {
                entry = tree.Entry;
                if (key > entry.Key)
                    map = tree.Right;
                else if (key < entry.Key)
                    map = tree.Left;
                else
                    return entry.Value;
            }

            if (map is ImMapBranch<V> branch)
                return branch.Entry.Key == key ? branch.Entry.Value
                    : branch.RightEntry.Key == key ? branch.RightEntry.Value
                    : default;

            entry = map as ImMapEntry<V>;
            if (entry != null && entry.Key == key)
                return entry.Value;

            return default;
        }

        /// <summary> Returns true if key is found and sets the value. </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static bool TryFind<V>(this ImMap<V> map, int key, out V value)
        {
            ImMapEntry<V> entry;
            while (map is ImMapTree<V> tree)
            {
                entry = tree.Entry;
                if (key > entry.Key)
                    map = tree.Right;
                else if (key < entry.Key)
                    map = tree.Left;
                else
                {
                    value = entry.Value;
                    return true;
                }
            }

            if (map is ImMapBranch<V> branch)
            {
                if (branch.Entry.Key == key)
                {
                    value = branch.Entry.Value;
                    return true;
                }

                if (branch.RightEntry.Key == key)
                {
                    value = branch.RightEntry.Value;
                    return true;
                }

                value = default;
                return false;
            }

            entry = map as ImMapEntry<V>;
            if (entry != null && entry.Key == key)
            {
                value = entry.Value;
                return true;
            }

            value = default;
            return false;
        }

        /// <summary> Returns true if key is found and sets the value. </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static bool TryFindEntry<V>(this ImMap<V> map, int key, out ImMapEntry<V> result)
        {
            ImMapEntry<V> entry;
            while (map is ImMapTree<V> tree)
            {
                entry = tree.Entry;
                if (key > entry.Key)
                    map = tree.Right;
                else if (key < entry.Key)
                    map = tree.Left;
                else
                {
                    result = entry;
                    return true;
                }
            }

            if (map is ImMapBranch<V> branch)
            {
                if (branch.Entry.Key == key)
                {
                    result = branch.Entry;
                    return true;
                }

                if (branch.RightEntry.Key == key)
                {
                    result = branch.RightEntry;
                    return true;
                }

                result = null;
                return false;
            }

            entry = map as ImMapEntry<V>;
            if (entry != null && entry.Key == key)
            {
                result = entry;
                return true;
            }

            result = null;
            return false;
        }

        /// <summary>
        /// Enumerates all the map nodes from the left to the right and from the bottom to top
        /// You may pass `parentStacks` to reuse the array memory.
        /// NOTE: the length of `parentStack` should be at least of map (height - 2) - the stack want be used for 0, 1, 2 height maps,
        /// the content of the stack is not important and could be erased.
        /// </summary>
        public static IEnumerable<ImMapEntry<V>> Enumerate<V>(this ImMap<V> map, ImMapTree<V>[] parentStack = null)
        {
            if (map == ImMap<V>.Empty)
                yield break;

            if (map is ImMapEntry<V> leaf)
                yield return leaf;
            else if (map is ImMapBranch<V> branch)
            {
                yield return branch.Entry;
                yield return branch.RightEntry;
            }
            else if (map is ImMapTree<V> tree)
            {
                if (tree.TreeHeight == 2)
                {
                    yield return (ImMapEntry<V>)tree.Left;
                    yield return tree.Entry;
                    yield return (ImMapEntry<V>)tree.Right;
                }
                else
                {
                    parentStack = parentStack ?? new ImMapTree<V>[tree.TreeHeight - 2];
                    var parentIndex = -1;
                    while (true)
                    {
                        if ((tree = map as ImMapTree<V>) != null)
                        {
                            if (tree.TreeHeight == 2)
                            {
                                yield return (ImMapEntry<V>)tree.Left;
                                yield return tree.Entry;
                                yield return (ImMapEntry<V>)tree.Right;
                                if (parentIndex == -1)
                                    break;
                                tree = parentStack[parentIndex--];
                                yield return tree.Entry;
                                map = tree.Right;
                            }
                            else
                            {
                                parentStack[++parentIndex] = tree;
                                map = tree.Left;
                            }
                        }
                        else if ((branch = map as ImMapBranch<V>) != null)
                        {
                            yield return branch.Entry;
                            yield return branch.RightEntry;
                            if (parentIndex == -1)
                                break;
                            tree = parentStack[parentIndex--];
                            yield return tree.Entry;
                            map = tree.Right;
                        }
                        else
                        {
                            yield return (ImMapEntry<V>)map;
                            if (parentIndex == -1)
                                break;
                            tree = parentStack[parentIndex--];
                            yield return tree.Entry;
                            map = tree.Right;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Folds all the map nodes with the state from left to right and from the bottom to top
        /// You may pass `parentStacks` to reuse the array memory.
        /// NOTE: the length of `parentStack` should be at least of map (height - 2) - the stack want be used for 0, 1, 2 height maps,
        /// the content of the stack is not important and could be erased.
        /// </summary>
        public static S Fold<V, S>(this ImMap<V> map, S state, Func<ImMapEntry<V>, S, S> reduce, ImMapTree<V>[] parentStack = null)
        {
            if (map == ImMap<V>.Empty)
                return state;

            if (map is ImMapEntry<V> leaf)
                state = reduce(leaf, state);
            else if (map is ImMapBranch<V> branch)
            {
                state = reduce(branch.Entry, state);
                state = reduce(branch.RightEntry, state);
            }
            else if (map is ImMapTree<V> tree)
            {
                if (tree.TreeHeight == 2)
                {
                    state = reduce((ImMapEntry<V>)tree.Left, state);
                    state = reduce(tree.Entry, state);
                    state = reduce((ImMapEntry<V>)tree.Right, state);
                }
                else
                {
                    parentStack = parentStack ?? new ImMapTree<V>[tree.TreeHeight - 2];
                    var parentIndex = -1;
                    while (true)
                    {
                        if ((tree = map as ImMapTree<V>) != null)
                        {
                            if (tree.TreeHeight == 2)
                            {
                                state = reduce((ImMapEntry<V>)tree.Left, state);
                                state = reduce(tree.Entry, state);
                                state = reduce((ImMapEntry<V>)tree.Right, state);
                                if (parentIndex == -1)
                                    break;
                                tree = parentStack[parentIndex--];
                                state = reduce(tree.Entry, state);
                                map = tree.Right;
                            }
                            else
                            {
                                parentStack[++parentIndex] = tree;
                                map = tree.Left;
                            }
                        }
                        else if ((branch = map as ImMapBranch<V>) != null)
                        {
                            state = reduce(branch.Entry, state);
                            state = reduce(branch.RightEntry, state);
                            if (parentIndex == -1)
                                break;
                            tree = parentStack[parentIndex--];
                            state = reduce(tree.Entry, state);
                            map = tree.Right;
                        }
                        else
                        {
                            state = reduce((ImMapEntry<V>)map, state);
                            if (parentIndex == -1)
                                break;
                            tree = parentStack[parentIndex--];
                            state = reduce(tree.Entry, state);
                            map = tree.Right;
                        }
                    }
                }
            }

            return state;
        }

        /// <summary>
        /// Folds all the map nodes with the state from left to right and from the bottom to top
        /// You may pass `parentStacks` to reuse the array memory.
        /// NOTE: the length of `parentStack` should be at least of map (height - 2) - the stack want be used for 0, 1, 2 height maps,
        /// the content of the stack is not important and could be erased.
        /// </summary>
        public static S Fold<V, S, A>(this ImMap<V> map, S state, A a, Func<ImMapEntry<V>, S, A, S> reduce, ImMapTree<V>[] parentStack = null)
        {
            if (map == ImMap<V>.Empty)
                return state;

            if (map is ImMapEntry<V> leaf)
                state = reduce(leaf, state, a);
            else if (map is ImMapBranch<V> branch)
            {
                state = reduce(branch.Entry, state, a);
                state = reduce(branch.RightEntry, state, a);
            }
            else if (map is ImMapTree<V> tree)
            {
                if (tree.TreeHeight == 2)
                {
                    state = reduce((ImMapEntry<V>)tree.Left, state, a);
                    state = reduce(tree.Entry, state, a);
                    state = reduce((ImMapEntry<V>)tree.Right, state, a);
                }
                else
                {
                    parentStack = parentStack ?? new ImMapTree<V>[tree.TreeHeight - 2];
                    var parentIndex = -1;
                    while (true)
                    {
                        if ((tree = map as ImMapTree<V>) != null)
                        {
                            if (tree.TreeHeight == 2)
                            {
                                state = reduce((ImMapEntry<V>)tree.Left, state, a);
                                state = reduce(tree.Entry, state, a);
                                state = reduce((ImMapEntry<V>)tree.Right, state, a);
                                if (parentIndex == -1)
                                    break;
                                tree = parentStack[parentIndex--];
                                state = reduce(tree.Entry, state, a);
                                map = tree.Right;
                            }
                            else
                            {
                                parentStack[++parentIndex] = tree;
                                map = tree.Left;
                            }
                        }
                        else if ((branch = map as ImMapBranch<V>) != null)
                        {
                            state = reduce(branch.Entry, state, a);
                            state = reduce(branch.RightEntry, state, a);
                            if (parentIndex == -1)
                                break;
                            tree = parentStack[parentIndex--];
                            state = reduce(tree.Entry, state, a);
                            map = tree.Right;
                        }
                        else
                        {
                            state = reduce((ImMapEntry<V>)map, state, a);
                            if (parentIndex == -1)
                                break;
                            tree = parentStack[parentIndex--];
                            state = reduce(tree.Entry, state, a);
                            map = tree.Right;
                        }
                    }
                }
            }

            return state;
        }

        /// <summary>
        /// Visits all the map nodes with from the left to the right and from the bottom to the top
        /// You may pass `parentStacks` to reuse the array memory.
        /// NOTE: the length of `parentStack` should be at least of map height, content is not important and could be erased.
        /// </summary>
        public static void Visit<V>(this ImMap<V> map, Action<ImMapEntry<V>> visit, ImMapTree<V>[] parentStack = null)
        {
            if (map == ImMap<V>.Empty)
                return;

            if (map is ImMapEntry<V> leaf)
                visit(leaf);
            else if (map is ImMapBranch<V> branch)
            {
                visit(branch.Entry);
                visit(branch.RightEntry);
            }
            else if (map is ImMapTree<V> tree)
            {
                if (tree.TreeHeight == 2)
                {
                    visit((ImMapEntry<V>)tree.Left);
                    visit(tree.Entry);
                    visit((ImMapEntry<V>)tree.Right);
                }
                else
                {
                    parentStack = parentStack ?? new ImMapTree<V>[tree.TreeHeight - 2];
                    var parentIndex = -1;
                    while (true)
                    {
                        if ((tree = map as ImMapTree<V>) != null)
                        {
                            if (tree.TreeHeight == 2)
                            {
                                visit((ImMapEntry<V>)tree.Left);
                                visit(tree.Entry);
                                visit((ImMapEntry<V>)tree.Right);
                                if (parentIndex == -1)
                                    break;
                                tree = parentStack[parentIndex--];
                                visit(tree.Entry);
                                map = tree.Right;
                            }
                            else
                            {
                                parentStack[++parentIndex] = tree;
                                map = tree.Left;
                            }
                        }
                        else if ((branch = map as ImMapBranch<V>) != null)
                        {
                            visit(branch.Entry);
                            visit(branch.RightEntry);
                            if (parentIndex == -1)
                                break;
                            tree = parentStack[parentIndex--];
                            visit(tree.Entry);
                            map = tree.Right;
                        }
                        else
                        {
                            visit((ImMapEntry<V>)map);
                            if (parentIndex == -1)
                                break;
                            tree = parentStack[parentIndex--];
                            visit(tree.Entry);
                            map = tree.Right;
                        }
                    }
                }
            }
        }

        /// <summary>Wraps Key and Value payload to store inside ImMapEntry</summary>
        public struct KValue<K>
        {
            /// <summary>The key</summary>
            public K Key;
            /// <summary>The value</summary>
            public object Value;

            /// <summary>Constructs a pair</summary>
            public KValue(K key, object value)
            {
                Key = key;
                Value = value;
            }
        }

        /// <summary>Uses the user provided hash and adds or updates the tree with passed key-value. Returns a new tree.</summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMap<KValue<K>> AddOrUpdate<K>(this ImMap<KValue<K>> map, int hash, K key, object value, Update<K, object> update)
        {
            var oldEntry = map.GetEntryOrDefault(hash);
            return oldEntry == null
                ? map.AddEntryUnsafe(CreateKValueEntry(hash, key, value))
                : UpdateEntryOrAddOrUpdateConflict(map, hash, oldEntry, key, value, update);
        }

        private static ImMap<KValue<K>> UpdateEntryOrAddOrUpdateConflict<K>(ImMap<KValue<K>> map, int hash,
            ImMapEntry<KValue<K>> oldEntry, K key, object value, Update<K, object> update = null)
        {
            if (key.Equals(oldEntry.Value.Key))
            {
                value = update == null ? value : update(key, oldEntry.Value.Value, value);
                return map.UpdateEntryUnsafe(CreateKValueEntry(hash, key, value));
            }

            // add a new conflicting key value
            ImMapEntry<KValue<K>>[] newConflicts;
            if (oldEntry.Value.Value is ImMapEntry<KValue<Type>>[] conflicts)
            {
                // entry is already containing the conflicted entries
                var conflictCount = conflicts.Length;
                var conflictIndex = conflictCount - 1;
                while (conflictIndex != -1 && !key.Equals(conflicts[conflictIndex].Value.Key))
                    --conflictIndex;

                if (conflictIndex != -1)
                {
                    // update the existing conflict
                    newConflicts = new ImMapEntry<KValue<K>>[conflictCount];
                    Array.Copy(conflicts, 0, newConflicts, 0, conflictCount);
                    value = update == null ? value : update(key, conflicts[conflictIndex].Value.Value, value);
                    newConflicts[conflictIndex] = CreateKValueEntry(hash, key, value);
                }
                else
                {
                    // add the new conflicting value
                    newConflicts = new ImMapEntry<KValue<K>>[conflictCount + 1];
                    Array.Copy(conflicts, 0, newConflicts, 0, conflictCount);
                    newConflicts[conflictCount] = CreateKValueEntry(hash, key, value);
                }
            }
            else
            {
                newConflicts = new[] { oldEntry, CreateKValueEntry(hash, key, value) };
            }

            var conflictsEntry = new ImMapEntry<KValue<K>>(hash);
            conflictsEntry.Value.Value = newConflicts;
            return map.UpdateEntryUnsafe(conflictsEntry);
        }

        /// <summary>Efficiently creates the new entry</summary>
        [MethodImpl((MethodImplOptions)256)]
        private static ImMapEntry<KValue<K>> CreateKValueEntry<K>(int hash, K key, object value)
        {
            var newEntry = new ImMapEntry<KValue<K>>(hash);
            newEntry.Value.Key = key;
            newEntry.Value.Value = value;
            return newEntry;
        }

        /// <summary>Efficiently creates the new entry</summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMapEntry<KValue<K>> CreateKValueEntry<K>(int hash, K key)
        {
            var newEntry = new ImMapEntry<KValue<K>>(hash);
            newEntry.Value.Key = key;
            return newEntry;
        }

        /// <summary>Uses the user provided hash and adds or updates the tree with passed key-value. Returns a new tree.</summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMap<KValue<K>> AddOrUpdate<K>(this ImMap<KValue<K>> map, int hash, K key, object value) =>
            map.AddOrUpdate(hash, CreateKValueEntry(hash, key, value));

        /// <summary>Uses the provided hash and adds or updates the tree with the passed key-value. Returns a new tree.</summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMap<KValue<K>> AddOrUpdate<K>(this ImMap<KValue<K>> map, int hash, ImMapEntry<KValue<K>> entry)
        {
            var oldEntry = map.GetEntryOrDefault(hash);
            return oldEntry == null
                ? map.AddEntryUnsafe(entry)
                : UpdateEntryOrAddOrUpdateConflict(map, hash, oldEntry, entry);
        }

        private static ImMap<KValue<K>> UpdateEntryOrAddOrUpdateConflict<K>(ImMap<KValue<K>> map, int hash,
            ImMapEntry<KValue<K>> oldEntry, ImMapEntry<KValue<K>> newEntry)
        {
            if (newEntry.Value.Key.Equals(oldEntry.Value.Key))
                return map.UpdateEntryUnsafe(newEntry);

            // add a new conflicting key value
            ImMapEntry<KValue<K>>[] newConflicts;
            if (oldEntry.Value.Value is ImMapEntry<KValue<Type>>[] conflicts)
            {
                // entry is already containing the conflicted entries
                var key = newEntry.Value.Key;
                var conflictCount = conflicts.Length;
                var conflictIndex = conflictCount - 1;
                while (conflictIndex != -1 && !key.Equals(conflicts[conflictIndex].Value.Key))
                    --conflictIndex;

                if (conflictIndex != -1)
                {
                    // update the existing conflict
                    newConflicts = new ImMapEntry<KValue<K>>[conflictCount];
                    Array.Copy(conflicts, 0, newConflicts, 0, conflictCount);
                    newConflicts[conflictIndex] = newEntry;
                }
                else
                {
                    // add the new conflicting value
                    newConflicts = new ImMapEntry<KValue<K>>[conflictCount + 1];
                    Array.Copy(conflicts, 0, newConflicts, 0, conflictCount);
                    newConflicts[conflictCount] = newEntry;
                }
            }
            else
            {
                newConflicts = new[] { oldEntry, newEntry };
            }

            return map.UpdateEntryUnsafe(CreateKValueEntry(hash, default(K), newConflicts));
        }

        /// <summary>Adds the new entry or keeps the current map if entry key is already present</summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMap<KValue<K>> AddOrKeep<K>(this ImMap<KValue<K>> map, int hash, K key)
        {
            var oldEntry = map.GetEntryOrDefault(hash);
            return oldEntry == null
                ? map.AddEntryUnsafe(CreateKValueEntry(hash, key))
                : AddOrKeepConflict(map, hash, oldEntry, key);
        }

        /// <summary>Adds the new entry or keeps the current map if entry key is already present</summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMap<KValue<K>> AddOrKeep<K>(this ImMap<KValue<K>> map, int hash, K key, object value)
        {
            var oldEntry = map.GetEntryOrDefault(hash);
            return oldEntry == null
                ? map.AddEntryUnsafe(CreateKValueEntry(hash, key, value))
                : AddOrKeepConflict(map, hash, oldEntry, key, value);
        }

        private static ImMap<KValue<K>> AddOrKeepConflict<K>(ImMap<KValue<K>> map, int hash,
            ImMapEntry<KValue<K>> oldEntry, K key, object value = null)
        {
            if (key.Equals(oldEntry.Value.Key))
                return map;

            // add a new conflicting key value
            ImMapEntry<KValue<K>>[] newConflicts;
            if (oldEntry.Value.Value is ImMapEntry<KValue<Type>>[] conflicts)
            {
                // entry is already containing the conflicted entries
                var conflictCount = conflicts.Length;
                var conflictIndex = conflictCount - 1;
                while (conflictIndex != -1 && !key.Equals(conflicts[conflictIndex].Value.Key))
                    --conflictIndex;

                if (conflictIndex != -1)
                    return map;
                
                // add the new conflicting value
                newConflicts = new ImMapEntry<KValue<K>>[conflictCount + 1];
                Array.Copy(conflicts, 0, newConflicts, 0, conflictCount);
                newConflicts[conflictCount] = CreateKValueEntry(hash, key, value);
            }
            else
            {
                newConflicts = new[] { oldEntry, CreateKValueEntry(hash, key, value) };
            }

            return map.UpdateEntryUnsafe(CreateKValueEntry(hash, default(K), newConflicts));
        }

        /// <summary>Updates the map with the new value if key is found, otherwise returns the same unchanged map.</summary>
        public static ImMap<KValue<K>> Update<K>(this ImMap<KValue<K>> map, int hash, K key, object value, Update<K, object> update = null)
        {
            var oldEntry = map.GetEntryOrDefault(hash);
            return oldEntry == null ? map : UpdateEntryOrReturnSelf(map, hash, oldEntry, key, value, update);
        }

        private static ImMap<KValue<K>> UpdateEntryOrReturnSelf<K>(ImMap<KValue<K>> map,
            int hash, ImMapEntry<KValue<K>> oldEntry, K key, object value, Update<K, object> update = null)
        {
            if (key.Equals(oldEntry.Value.Key))
            {
                value = update == null ? value : update(key, oldEntry.Value.Value, value);
                return map.UpdateEntryUnsafe(CreateKValueEntry(hash, key, value));
            }

            // add a new conflicting key value
            ImMapEntry<KValue<K>>[] newConflicts;
            if (oldEntry.Value.Value is ImMapEntry<KValue<Type>>[] conflicts)
            {
                // entry is already containing the conflicted entries
                var conflictCount = conflicts.Length;
                var conflictIndex = conflictCount - 1;
                while (conflictIndex != -1 && !key.Equals(conflicts[conflictIndex].Value.Key))
                    --conflictIndex;

                if (conflictIndex == -1)
                    return map;

                // update the existing conflict
                newConflicts = new ImMapEntry<KValue<K>>[conflictCount];
                Array.Copy(conflicts, 0, newConflicts, 0, conflictCount);
                value = update == null ? value : update(key, conflicts[conflictIndex].Value.Value, value);
                newConflicts[conflictIndex] = CreateKValueEntry(hash, key, value);
            }
            else
            {
                return map;
            }

            return map.UpdateEntryUnsafe(CreateKValueEntry(hash, default(K), newConflicts));
        }

        /// <summary>Updates the map with the default value if the key is found, otherwise returns the same unchanged map.</summary>
        public static ImMap<KValue<K>> UpdateToDefault<K>(this ImMap<KValue<K>> map, int hash, K key)
        {
            var oldEntry = map.GetEntryOrDefault(hash);
            return oldEntry == null ? map : UpdateEntryOrReturnSelf(map, hash, oldEntry, key);
        }

        private static ImMap<KValue<K>> UpdateEntryOrReturnSelf<K>(ImMap<KValue<K>> map,
            int hash, ImMapEntry<KValue<K>> oldEntry, K key)
        {
            if (key.Equals(oldEntry.Value.Key))
                return map.UpdateEntryUnsafe(CreateKValueEntry(hash, key));

            // add a new conflicting key value
            ImMapEntry<KValue<K>>[] newConflicts;
            if (oldEntry.Value.Value is ImMapEntry<KValue<Type>>[] conflicts)
            {
                // entry is already containing the conflicted entries
                var conflictCount = conflicts.Length;
                var conflictIndex = conflictCount - 1;
                while (conflictIndex != -1 && !key.Equals(conflicts[conflictIndex].Value.Key))
                    --conflictIndex;

                if (conflictIndex == -1)
                    return map;

                // update the existing conflict
                newConflicts = new ImMapEntry<KValue<K>>[conflictCount];
                Array.Copy(conflicts, 0, newConflicts, 0, conflictCount);
                newConflicts[conflictIndex] = CreateKValueEntry(hash, key);
            }
            else
            {
                return map;
            }

            var conflictsEntry = new ImMapEntry<KValue<K>>(hash);
            conflictsEntry.Value.Value = newConflicts;
            return map.UpdateEntryUnsafe(conflictsEntry);
        }

        /// <summary> Returns the entry if key is found or default value otherwise. </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMapEntry<KValue<K>> GetEntryOrDefault<K>(this ImMap<KValue<K>> map, int hash, K key)
        {
            var entry = map.GetEntryOrDefault(hash);
            return entry != null
                ? key.Equals(entry.Value.Key) ? entry : GetConflictedEntryOrDefault(entry, key)
                : null;
        }

        /// <summary> Returns the value if key is found or default value otherwise. </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static object GetValueOrDefault<K>(this ImMap<KValue<K>> map, int hash, K key) =>
            map.GetEntryOrDefault(hash, key)?.Value.Value;

        /// <summary> Sets the value if key is found or returns false otherwise. </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static bool TryFind<K>(this ImMap<KValue<K>> map, int hash, K key, out object value)
        {
            var entry = map.GetEntryOrDefault(hash, key);
            if (entry != null)
            {
                value = entry.Value.Value;
                return true;
            }

            value = null;
            return false;
        }

        /// <summary> Returns the entry if key is found or `null` otherwise. </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static ImMapEntry<KValue<Type>> GetEntryOrDefault(this ImMap<KValue<Type>> map, int hash, Type type)
        {
            var entry = map.GetEntryOrDefault(hash);
            return entry != null
                ? entry.Value.Key == type ? entry : GetConflictedEntryOrDefault(entry, type)
                : null;
        }

        /// <summary> Returns the value if the Type key is found or default value otherwise. </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static object GetValueOrDefault(this ImMap<KValue<Type>> map, int hash, Type typeKey) =>
            map.GetEntryOrDefault(hash, typeKey)?.Value.Value;

        /// <summary> Returns the value if the Type key is found or default value otherwise. </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static object GetValueOrDefault(this ImMap<KValue<Type>> map, Type typeKey) =>
            map.GetEntryOrDefault(RuntimeHelpers.GetHashCode(typeKey), typeKey)?.Value.Value;

        internal static ImMapEntry<KValue<K>> GetConflictedEntryOrDefault<K>(ImMapEntry<KValue<K>> entry, K key)
        {
            if (entry.Value.Value is ImMapEntry<KValue<K>>[] conflicts)
                for (var i = 0; i < conflicts.Length; ++i)
                    if (key.Equals(conflicts[i].Value.Key))
                        return conflicts[i];
            return null;
        }

        /// <summary>
        /// Depth-first in-order traversal as described in http://en.wikipedia.org/wiki/Tree_traversal
        /// The only difference is using fixed size array instead of stack for speed-up.
        /// </summary>
        public static IEnumerable<ImMapEntry<KValue<K>>> Enumerate<K>(this ImMap<KValue<K>> map)
        {
            foreach (var entry in map.Enumerate(null))
            {
                if (entry.Value.Value is ImMapEntry<KValue<K>>[] conflicts)
                    for (var i = 0; i < conflicts.Length; i++)
                        yield return conflicts[i];
                else
                    yield return entry;
            }
        }

        /// <summary>
        /// Depth-first in-order traversal as described in http://en.wikipedia.org/wiki/Tree_traversal
        /// The only difference is using fixed size array instead of stack for speed-up.
        /// Note: By passing <paramref name="parentsStack"/> you may reuse the stack array between different method calls,
        /// but it should be at least <see cref="ImHashMap{K,V}.Height"/> length. The contents of array are not important.
        /// </summary>
        public static S Fold<K, S>(this ImMap<KValue<K>> map,
            S state, Func<ImMapEntry<KValue<K>>, S, S> reduce, ImMapTree<KValue<K>>[] parentsStack = null) =>
                map.Fold(state, reduce, (entry, s, r) =>
                {
                    if (entry.Value.Value is ImMapEntry<KValue<K>>[] conflicts)
                        for (var i = 0; i < conflicts.Length; i++)
                            s = r(conflicts[i], s);
                    else
                        s = r(entry, s);
                    return s;
                },
                parentsStack);

        /// <summary>
        /// Depth-first in-order traversal as described in http://en.wikipedia.org/wiki/Tree_traversal
        /// The only difference is using fixed size array instead of stack for speed-up.
        /// Note: By passing <paramref name="parentsStack"/> you may reuse the stack array between different method calls,
        /// but it should be at least <see cref="ImHashMap{K,V}.Height"/> length. The contents of array are not important.
        /// </summary>
        public static S Visit<K, S>(this ImMap<KValue<K>> map,
            S state, Action<ImMapEntry<KValue<K>>, S> effect, ImMapTree<KValue<K>>[] parentsStack = null) =>
            map.Fold(state, effect, (entry, s, eff) =>
            {
                if (entry.Value.Value is ImMapEntry<KValue<K>>[] conflicts)
                    for (var i = 0; i < conflicts.Length; i++)
                        eff(conflicts[i], s);
                else
                    eff(entry, s);
                return s;
            },
            parentsStack);

        /// <summary>
        /// Depth-first in-order traversal as described in http://en.wikipedia.org/wiki/Tree_traversal
        /// The only difference is using fixed size array instead of stack for speed-up.
        /// Note: By passing <paramref name="parentsStack"/> you may reuse the stack array between different method calls,
        /// but it should be at least <see cref="ImHashMap{K,V}.Height"/> length. The contents of array are not important.
        /// </summary>
        public static void Visit<K>(this ImMap<KValue<K>> map,
            Action<ImMapEntry<KValue<K>>> effect, ImMapTree<KValue<K>>[] parentsStack = null) =>
            map.Fold(false, effect, (entry, s, eff) =>
            {
                if (entry.Value.Value is ImMapEntry<KValue<K>>[] conflicts)
                    for (var i = 0; i < conflicts.Length; i++)
                        eff(conflicts[i]);
                else
                    eff(entry);
                return false;
            },
            parentsStack);
    }

    /// <summary>
    /// The array of ImMap slots where the key first bits are used for FAST slot location
    /// and the slot is the reference to ImMap that can be swapped with its updated value
    /// </summary>
    public static class ImMapSlots
    {
        /// Default number of slots
        public const int SLOT_COUNT_POWER_OF_TWO = 32;

        /// The default mask to partition the key to the target slot
        public const int KEY_MASK_TO_FIND_SLOT = SLOT_COUNT_POWER_OF_TWO - 1;

        /// Creates the array with the empty slots
        [MethodImpl((MethodImplOptions)256)]
        public static ImMap<V>[] CreateWithEmpty<V>(int slotCountPowerOfTwo = SLOT_COUNT_POWER_OF_TWO)
        {
            var slots = new ImMap<V>[slotCountPowerOfTwo];
            for (var i = 0; i < slots.Length; ++i)
                slots[i] = ImMap<V>.Empty;
            return slots;
        }

        /// Returns a new tree with added or updated value for specified key.
        [MethodImpl((MethodImplOptions)256)]
        public static void AddOrUpdate<V>(this ImMap<V>[] slots, int key, V value, int keyMaskToFindSlot = KEY_MASK_TO_FIND_SLOT)
        {
            ref var slot = ref slots[key & keyMaskToFindSlot];
            var copy = slot;
            if (Interlocked.CompareExchange(ref slot, copy.AddOrUpdate(key, value), copy) != copy)
                RefAddOrUpdateSlot(ref slot, key, value);
        }

        /// Update the ref to the slot with the new version - retry if the someone changed the slot in between
        public static void RefAddOrUpdateSlot<V>(ref ImMap<V> slot, int key, V value) =>
            Ref.Swap(ref slot, key, value, (x, k, v) => x.AddOrUpdate(k, v));

        /// Adds a new value for the specified key or keeps the existing map if the key is already in the map.
        [MethodImpl((MethodImplOptions)256)]
        public static void AddOrKeep<V>(this ImMap<V>[] slots, int key, V value, int keyMaskToFindSlot = KEY_MASK_TO_FIND_SLOT)
        {
            ref var slot = ref slots[key & keyMaskToFindSlot];
            var copy = slot;
            if (Interlocked.CompareExchange(ref slot, copy.AddOrKeep(key, value), copy) != copy)
                RefAddOrKeepSlot(ref slot, key, value);
        }

        /// Update the ref to the slot with the new version - retry if the someone changed the slot in between
        public static void RefAddOrKeepSlot<V>(ref ImMap<V> slot, int key, V value) =>
            Ref.Swap(ref slot, key, value, (s, k, v) => s.AddOrKeep(k, v));

        /// Adds a default value entry for the specified key or keeps the existing map if the key is already in the map.
        [MethodImpl((MethodImplOptions)256)]
        public static void AddOrKeep<V>(this ImMap<V>[] slots, int key, int keyMaskToFindSlot = KEY_MASK_TO_FIND_SLOT)
        {
            ref var slot = ref slots[key & keyMaskToFindSlot];
            var copy = slot;
            if (Interlocked.CompareExchange(ref slot, copy.AddOrKeep(key), copy) != copy)
                RefAddOrKeepSlot(ref slot, key);
        }

        /// Update the ref to the slot with the new version - retry if the someone changed the slot in between
        public static void RefAddOrKeepSlot<V>(ref ImMap<V> slot, int key) =>
            Ref.Swap(ref slot, key, (s, k) => s.AddOrKeep(k));

        /// <summary> Folds all map nodes without the order </summary>
        public static S Fold<V, S>(this ImMap<V>[] slots, S state, Func<ImMapEntry<V>, S, S> reduce)
        {
            var parentStack = ArrayTools.Empty<ImMapTree<V>>();
            for (var i = 0; i < slots.Length; ++i)
            {
                var map = slots[i];
                if (map == ImMap<V>.Empty)
                    continue;

                if (map is ImMapEntry<V> leaf)
                    state = reduce(leaf, state);
                else if (map is ImMapBranch<V> branch)
                {
                    state = reduce(branch.Entry, state);
                    state = reduce(branch.RightEntry, state);
                }
                else if (map is ImMapTree<V> tree)
                {
                    if (tree.TreeHeight == 2)
                    {
                        state = reduce((ImMapEntry<V>)tree.Left, state);
                        state = reduce(tree.Entry, state);
                        state = reduce((ImMapEntry<V>)tree.Right, state);
                    }
                    else
                    {
                        if (parentStack.Length < tree.TreeHeight - 2)
                            parentStack = new ImMapTree<V>[tree.TreeHeight - 2];
                        var parentIndex = -1;
                        while (true)
                        {
                            if ((tree = map as ImMapTree<V>) != null)
                            {
                                if (tree.TreeHeight == 2)
                                {
                                    state = reduce((ImMapEntry<V>)tree.Left, state);
                                    state = reduce(tree.Entry, state);
                                    state = reduce((ImMapEntry<V>)tree.Right, state);
                                    if (parentIndex == -1)
                                        break;
                                    tree = parentStack[parentIndex--];
                                    state = reduce(tree.Entry, state);
                                    map = tree.Right;
                                }
                                else
                                {
                                    parentStack[++parentIndex] = tree;
                                    map = tree.Left;
                                }
                            }
                            else if ((branch = map as ImMapBranch<V>) != null)
                            {
                                state = reduce(branch.Entry, state);
                                state = reduce(branch.RightEntry, state);
                                if (parentIndex == -1)
                                    break;
                                tree = parentStack[parentIndex--];
                                state = reduce(tree.Entry, state);
                                map = tree.Right;
                            }
                            else
                            {
                                state = reduce((ImMapEntry<V>)map, state);
                                if (parentIndex == -1)
                                    break;
                                tree = parentStack[parentIndex--];
                                state = reduce(tree.Entry, state);
                                map = tree.Right;
                            }
                        }
                    }
                }
            }

            return state;
        }
    }

    /// <summary>Wraps the stored data with "fixed" reference semantics - when added to the tree it did not change or reconstructed in memory</summary>
    public class ImHashMapEntry<K, V>
    {
        /// Empty thingy
        public static readonly ImHashMapEntry<K, V> Empty = new ImHashMapEntry<K, V>();

        /// Key hash
        public readonly int Hash;

        ///  The key
        public readonly K Key;

        /// The value - may be mutated implementing the Ref CAS semantics if needed
        public V Value;

        private ImHashMapEntry() { }

        /// Constructs the data
        public ImHashMapEntry(int hash, K key, V value)
        {
            Hash = hash;
            Key = key;
            Value = value;
        }

        /// Constructs the data with the default value
        public ImHashMapEntry(int hash, K key)
        {
            Hash = hash;
            Key = key;
        }

        /// <summary>Outputs the brief tree info - mostly for debugging purposes</summary>
        public override string ToString() => Key + ": " + Value;
    }

    /// Stores ALL the data in `Conflicts` array, the fields except the `hash` are just fillers.
    /// This way we preserve the once created `ImHashMapData` so that client can hold the reference to it and update the Value if needed.
    public sealed class ImHashMapConflicts<K, V> : ImHashMapEntry<K, V>
    {
        /// Conflicted data
        public readonly ImHashMapEntry<K, V>[] Conflicts;

        /// <inheritdoc />
        public ImHashMapConflicts(int hash, params ImHashMapEntry<K, V>[] conflicts) : base(hash, default, default) =>
            Conflicts = conflicts;
    }

    /// Immutable http://en.wikipedia.org/wiki/AVL_tree 
    /// where node key is the hash code of <typeparamref name="K"/>
    public sealed class ImHashMap<K, V>
    {
        /// Empty map to start with.
        public static readonly ImHashMap<K, V> Empty = new ImHashMap<K, V>();

        /// <summary>Calculated key hash.</summary>
        public int Hash
        {
            [MethodImpl((MethodImplOptions)256)]
            get => Entry.Hash;
        }

        /// <summary>Key of type K that should support <see cref="object.Equals(object)"/> and <see cref="object.GetHashCode"/>.</summary>
        public K Key
        {
            [MethodImpl((MethodImplOptions)256)]
            get => Entry.Key;
        }

        /// <summary>Value of any type V.</summary>
        public V Value
        {
            [MethodImpl((MethodImplOptions)256)]
            get => Entry.Value;
        }

        /// <summary>In case of <see cref="Hash"/> conflicts for different keys contains conflicted keys with their values.</summary>
        public ImHashMapEntry<K, V>[] Conflicts
        {
            [MethodImpl((MethodImplOptions)256)]
            get => (Entry as ImHashMapConflicts<K, V>)?.Conflicts;
        }

        /// <summary>Left sub-tree/branch, or empty.</summary>
        public ImHashMap<K, V> Left;

        /// <summary>Right sub-tree/branch, or empty.</summary>
        public ImHashMap<K, V> Right;

        /// <summary>Height of longest sub-tree/branch plus 1. It is 0 for empty tree, and 1 for single node tree.</summary>
        public int Height;

        /// <summary>Returns true if tree is empty.</summary>
        public bool IsEmpty => Height == 0;

        /// <summary>The entry which is allocated once and can be used as a "fixed" reference to the Key and Value</summary>
        public readonly ImHashMapEntry<K, V> Entry;

        internal ImHashMap() => Entry = ImHashMapEntry<K, V>.Empty;

        /// Creates  leaf node
        public ImHashMap(int hash, K key, V value)
        {
            Entry = new ImHashMapEntry<K, V>(hash, key, value);
            Left = Empty;
            Right = Empty;
            Height = 1;
        }

        /// Creates a leaf node with default value
        public ImHashMap(int hash, K key)
        {
            Entry = new ImHashMapEntry<K, V>(hash, key);
            Left = Empty;
            Right = Empty;
            Height = 1;
        }

        /// Creates a leaf node
        public ImHashMap(ImHashMapEntry<K, V> entry)
        {
            Entry = entry;
            Left = Empty;
            Right = Empty;
            Height = 1;
        }

        /// Creates the tree and calculates the height for you
        public ImHashMap(ImHashMapEntry<K, V> entry, ImHashMap<K, V> left, ImHashMap<K, V> right)
        {
            Entry = entry;
            Left = left;
            Right = right;
            Height = 1 + (left.Height > right.Height ? left.Height : right.Height);
        }

        /// Creates the tree with the known height
        public ImHashMap(ImHashMapEntry<K, V> entry, ImHashMap<K, V> left, ImHashMap<K, V> right, int height)
        {
            Entry = entry;
            Left = left;
            Right = right;
            Height = height;
        }

        /// <summary>Outputs the brief tree info - mostly for debugging purposes</summary>
        public override string ToString() => Height == 0 ? "empty"
            : "(" + Entry
            + ") -> (" + (Left.Height == 0 ? "empty" : Left.Entry + " of height " + Left.Height)
            + ", " + (Right.Height == 0 ? "empty" : Right.Entry + " of height " + Right.Height)
            + ")";

        /// <summary>Uses the user provided hash and adds and updates the tree with passed key-value. Returns a new tree.</summary>
        [MethodImpl((MethodImplOptions)256)]
        public ImHashMap<K, V> AddOrUpdate(int hash, K key, V value) =>
            Height == 0 ? new ImHashMap<K, V>(hash, key, value)
            : hash == Hash ? UpdateValueOrAddOrUpdateConflict(hash, key, value)
            : AddOrUpdateLeftOrRight(hash, key, value);

        /// Adds and updates the tree with passed key-value. Returns a new tree.
        [MethodImpl((MethodImplOptions)256)]
        public ImHashMap<K, V> AddOrUpdate(K key, V value) =>
            AddOrUpdate(key.GetHashCode(), key, value);

        private ImHashMap<K, V> UpdateValueOrAddOrUpdateConflict(int hash, K key, V value)
        {
            var conflictsData = Entry as ImHashMapConflicts<K, V>;
            return conflictsData == null && (ReferenceEquals(key, Key) || key.Equals(Key))
                ? new ImHashMap<K, V>(new ImHashMapEntry<K, V>(hash, key, value), Left, Right, Height)
                : AddOrUpdateConflict(conflictsData, hash, key, value);
        }

        internal enum DoAddOrUpdateConflicts { AddOrUpdate, AddOrKeep, Update }

        private ImHashMap<K, V> AddOrUpdateConflict(ImHashMapConflicts<K, V> conflictsData, int hash, K key, V value,
            Update<K, V> update = null, DoAddOrUpdateConflicts doWhat = DoAddOrUpdateConflicts.AddOrUpdate)
        {
            if (conflictsData == null)
                return doWhat == DoAddOrUpdateConflicts.Update
                    ? this
                    : new ImHashMap<K, V>(
                        new ImHashMapConflicts<K, V>(hash, Entry, new ImHashMapEntry<K, V>(hash, key, value)),
                        Left, Right, Height);

            var conflicts = conflictsData.Conflicts;
            var conflictCount = conflicts.Length;
            var conflictIndex = conflictCount - 1;
            while (conflictIndex != -1 && !key.Equals(conflicts[conflictIndex].Key))
                --conflictIndex;

            ImHashMapEntry<K, V>[] newConflicts;
            if (conflictIndex != -1)
            {
                if (doWhat == DoAddOrUpdateConflicts.AddOrKeep)
                    return this;

                // update the existing conflicted value
                newConflicts = new ImHashMapEntry<K, V>[conflictCount];
                Array.Copy(conflicts, 0, newConflicts, 0, conflictCount);
                var newValue = update == null ? value : update(key, conflicts[conflictIndex].Value, value);
                newConflicts[conflictIndex] = new ImHashMapEntry<K, V>(hash, key, newValue);
            }
            else
            {
                if (doWhat == DoAddOrUpdateConflicts.Update)
                    return this;

                // add the new conflicting value
                newConflicts = new ImHashMapEntry<K, V>[conflictCount + 1];
                Array.Copy(conflicts, 0, newConflicts, 0, conflictCount);
                newConflicts[conflictCount] = new ImHashMapEntry<K, V>(hash, key, value);
            }

            return new ImHashMap<K, V>(new ImHashMapConflicts<K, V>(hash, newConflicts), Left, Right, Height);
        }

        private ImHashMap<K, V> AddOrUpdateLeftOrRight(int hash, K key, V value)
        {
            if (hash < Hash)
            {
                if (Left.Height == 0)
                    return new ImHashMap<K, V>(Entry, new ImHashMap<K, V>(hash, key, value), Right, 2);

                if (Left.Hash == hash)
                    return new ImHashMap<K, V>(Entry, Left.UpdateValueOrAddOrUpdateConflict(hash, key, value), Right, Height);

                if (Right.Height == 0)
                {
                    if (hash < Left.Hash)
                        return new ImHashMap<K, V>(Left.Entry,
                            new ImHashMap<K, V>(hash, key, value), new ImHashMap<K, V>(Entry), 2);

                    return new ImHashMap<K, V>(new ImHashMapEntry<K, V>(hash, key, value),
                        new ImHashMap<K, V>(Left.Entry), new ImHashMap<K, V>(Entry), 2);
                }

                var left = Left.AddOrUpdateLeftOrRight(hash, key, value);
                return left.Height > Right.Height + 1
                    ? BalanceNewLeftTree(left)
                    : new ImHashMap<K, V>(Entry, left, Right);
            }
            else
            {
                if (Right.Height == 0)
                    return new ImHashMap<K, V>(Entry, Left, new ImHashMap<K, V>(hash, key, value), 2);

                if (Right.Hash == hash)
                    return new ImHashMap<K, V>(Entry, Left, Right.UpdateValueOrAddOrUpdateConflict(hash, key, value), Height);

                if (Left.Height == 0)
                {
                    if (hash < Right.Hash)
                        return new ImHashMap<K, V>(new ImHashMapEntry<K, V>(hash, key, value),
                            new ImHashMap<K, V>(Entry), new ImHashMap<K, V>(Right.Entry), 2);

                    return new ImHashMap<K, V>(Right.Entry,
                        new ImHashMap<K, V>(Entry), new ImHashMap<K, V>(hash, key, value), 2);
                }

                var right = Right.AddOrUpdateLeftOrRight(hash, key, value);
                return right.Height > Left.Height + 1
                    ? BalanceNewRightTree(right)
                    : new ImHashMap<K, V>(Entry, Left, right);
            }
        }

        private ImHashMap<K, V> BalanceNewLeftTree(ImHashMap<K, V> newLeftTree)
        {
            var leftLeft = newLeftTree.Left;
            var leftLeftHeight = leftLeft.Height;

            var leftRight = newLeftTree.Right;
            var leftRightHeight = leftRight.Height;

            if (leftRightHeight > leftLeftHeight)
            {
                newLeftTree.Right = leftRight.Left;
                newLeftTree.Height = leftLeftHeight + 1;
                return new ImHashMap<K, V>(leftRight.Entry,
                    newLeftTree,
                    new ImHashMap<K, V>(Entry, leftRight.Right, Right, Right.Height + 1),
                    leftLeftHeight + 2);

                //return new ImHashMap<K, V>(leftRight.Entry,
                //    new ImHashMap<K, V>(newLeftTree.Entry, leftLeft, leftRight.Left),
                //    new ImHashMap<K, V>(Entry, leftRight.Right, Right));
            }

            newLeftTree.Right = new ImHashMap<K, V>(Entry, leftRight, Right, leftRightHeight + 1);
            newLeftTree.Height = leftRightHeight + 2;
            return newLeftTree;

            //return new ImHashMap<K, V>(newLeftTree.Entry,
            //    leftLeft, new ImHashMap<K, V>(Entry, leftRight, Right));
        }

        // Note that Left is by 2 less deep than `newRightTree` - means that at `newRightTree.Left/Right` is at least of Left height or deeper
        private ImHashMap<K, V> BalanceNewRightTree(ImHashMap<K, V> newRightTree)
        {
            var rightLeft = newRightTree.Left;
            var rightLeftHeight = rightLeft.Height;

            var rightRight = newRightTree.Right;
            var rightRightHeight = rightRight.Height;

            if (rightLeftHeight > rightRightHeight) // 1 greater - not 2 greater because it would be too unbalanced
            {
                newRightTree.Left = rightLeft.Right;
                // the height now should be defined by rr - because left now is shorter by 1
                newRightTree.Height = rightRightHeight + 1;
                // the whole height consequentially can be defined by `newRightTree` (rr+1) because left is consist of short Left and -2 rl.Left
                return new ImHashMap<K, V>(rightLeft.Entry,
                    // Left should be >= rightLeft.Left because it maybe rightLeft.Right which defines rl height
                    new ImHashMap<K, V>(Entry, Left, rightLeft.Left, height: Left.Height + 1),
                    newRightTree, rightRightHeight + 2);

                //return new ImHashMap<K, V>(rightLeft.Entry,
                //    new ImHashMap<K, V>(Entry, Left, rightLeft.Left),
                //    new ImHashMap<K, V>(newRightTree.Entry, rightLeft.Right, rightRight));
            }

            // we may decide on the height because the Left smaller by 2
            newRightTree.Left = new ImHashMap<K, V>(Entry, Left, rightLeft, rightLeftHeight + 1);
            // if rr was > rl by 1 than new rl+1 should be equal height to rr now, if rr was == rl than new rl wins anyway
            newRightTree.Height = rightLeftHeight + 2;
            return newRightTree;

            //return new ImHashMap<K, V>(newRightTree.Entry, new ImHashMap<K, V>(Entry, Left, rightLeft), rightRight);
        }

        /// Uses the user provided hash and adds and updates the tree with passed key-value and the update function for the existing value. Returns a new tree.
        [MethodImpl((MethodImplOptions)256)]
        public ImHashMap<K, V> AddOrUpdate(int hash, K key, V value, Update<K, V> update) =>
            Height == 0 ? new ImHashMap<K, V>(hash, key, value)
            : hash == Hash ? UpdateValueOrAddOrUpdateConflict(hash, key, value, update)
            : AddOrUpdateLeftOrRightWithUpdate(hash, key, value, update);

        private ImHashMap<K, V> UpdateValueOrAddOrUpdateConflict(int hash, K key, V value, Update<K, V> update)
        {
            var conflictsData = Entry as ImHashMapConflicts<K, V>;
            return conflictsData == null && (ReferenceEquals(Key, key) || Key.Equals(key))
                ? new ImHashMap<K, V>(new ImHashMapEntry<K, V>(hash, key, update(key, Value, value)), Left, Right, Height)
                : AddOrUpdateConflict(conflictsData, hash, key, value, update);
        }

        private ImHashMap<K, V> AddOrUpdateLeftOrRightWithUpdate(int hash, K key, V value, Update<K, V> update)
        {
            if (hash < Hash)
            {
                if (Left.Height == 0)
                    return new ImHashMap<K, V>(Entry, new ImHashMap<K, V>(hash, key, value), Right, 2);

                if (Left.Hash == hash)
                    return new ImHashMap<K, V>(Entry, Left.UpdateValueOrAddOrUpdateConflict(hash, key, value, update), Right, Height);

                if (Right.Height == 0)
                {
                    if (hash < Left.Hash)
                        return new ImHashMap<K, V>(Left.Entry, new ImHashMap<K, V>(hash, key, value), new ImHashMap<K, V>(Entry), 2);

                    return new ImHashMap<K, V>(new ImHashMapEntry<K, V>(hash, key, value),
                        new ImHashMap<K, V>(Left.Entry), new ImHashMap<K, V>(Entry), 2);
                }

                var left = Left.AddOrUpdateLeftOrRightWithUpdate(hash, key, value, update);
                return left.Height > Right.Height + 1
                    ? BalanceNewLeftTree(left)
                    : new ImHashMap<K, V>(Entry, left, Right);
            }
            else
            {
                if (Right.Height == 0)
                    return new ImHashMap<K, V>(Entry, Left, new ImHashMap<K, V>(hash, key, value), 2);

                if (Right.Hash == hash)
                    return new ImHashMap<K, V>(Entry, Left, Right.UpdateValueOrAddOrUpdateConflict(hash, key, value, update), Height);

                if (Left.Height == 0)
                {
                    if (hash < Right.Hash)
                        return new ImHashMap<K, V>(new ImHashMapEntry<K, V>(hash, key, value),
                            new ImHashMap<K, V>(Entry), new ImHashMap<K, V>(Right.Entry), 2);

                    return new ImHashMap<K, V>(Right.Entry,
                        new ImHashMap<K, V>(Entry), new ImHashMap<K, V>(hash, key, value), 2);
                }

                var right = Right.AddOrUpdateLeftOrRightWithUpdate(hash, key, value, update);
                return right.Height > Left.Height + 1
                    ? BalanceNewRightTree(right)
                    : new ImHashMap<K, V>(Entry, Left, right);
            }
        }

        /// Returns a new tree with added or updated key-value. Uses the provided <paramref name="update"/> for updating the existing value.
        [MethodImpl((MethodImplOptions)256)]
        public ImHashMap<K, V> AddOrUpdate(K key, V value, Update<K, V> update) =>
            AddOrUpdate(key.GetHashCode(), key, value, update);

        /// Returns a new tree with added or updated key-value. Uses the provided <paramref name="update"/> for updating the existing value.
        [MethodImpl((MethodImplOptions)256)]
        public ImHashMap<K, V> AddOrUpdate(K key, V value, Update<V> update) =>
            AddOrUpdate(key.GetHashCode(), key, value, update.IgnoreKey);

        /// Adds a new value for the specified key or keeps the existing map if the key is already in the map.
        [MethodImpl((MethodImplOptions)256)]
        public ImHashMap<K, V> AddOrKeep(int hash, K key, V value) =>
            Height == 0 ? new ImHashMap<K, V>(hash, key, value)
            : hash == Hash ? KeepValueOrAddConflict(hash, key, value)
            : AddOrKeepLeftOrRight(hash, key, value);

        /// Adds a new value for the specified key or keeps the existing map if the key is already in the map.
        [MethodImpl((MethodImplOptions)256)]
        public ImHashMap<K, V> AddOrKeep(K key, V value) =>
            AddOrKeep(key.GetHashCode(), key, value);

        private ImHashMap<K, V> KeepValueOrAddConflict(int hash, K key, V value)
        {
            var conflictsData = Entry as ImHashMapConflicts<K, V>;
            return conflictsData == null && (ReferenceEquals(Key, key) || Key.Equals(key)) ? this
                : AddOrUpdateConflict(conflictsData, hash, key, value, null, DoAddOrUpdateConflicts.AddOrKeep);
        }
        private ImHashMap<K, V> AddOrKeepLeftOrRight(int hash, K key, V value)
        {
            if (hash < Hash)
            {
                if (Left.Height == 0)
                    return new ImHashMap<K, V>(Entry, new ImHashMap<K, V>(hash, key, value), Right, 2);

                if (Left.Hash == hash)
                {
                    var leftWithNewConflict = Left.KeepValueOrAddConflict(hash, key, value);
                    return ReferenceEquals(leftWithNewConflict, Left) ? this
                        : new ImHashMap<K, V>(Entry, leftWithNewConflict, Right, Height);
                }

                if (Right.Height == 0)
                {
                    if (hash < Left.Hash)
                        return new ImHashMap<K, V>(Left.Entry,
                            new ImHashMap<K, V>(hash, key, value), new ImHashMap<K, V>(Entry), 2);

                    return new ImHashMap<K, V>(new ImHashMapEntry<K, V>(hash, key, value),
                        new ImHashMap<K, V>(Left.Entry), new ImHashMap<K, V>(Entry), 2);
                }

                var left = Left.AddOrKeepLeftOrRight(hash, key, value);
                if (ReferenceEquals(left, Left))
                    return this;

                return left.Height > Right.Height + 1
                    ? BalanceNewLeftTree(left)
                    : new ImHashMap<K, V>(Entry, left, Right);
            }
            else
            {
                if (Right.Height == 0)
                    return new ImHashMap<K, V>(Entry, Left, new ImHashMap<K, V>(hash, key, value), 2);

                if (Right.Hash == hash)
                {
                    var rightWithNewConflict = Right.KeepValueOrAddConflict(hash, key, value);
                    return ReferenceEquals(rightWithNewConflict, Right) ? this
                        : new ImHashMap<K, V>(Entry, Left, rightWithNewConflict, Height);
                }

                if (Left.Height == 0)
                {
                    if (hash < Right.Hash)
                        return new ImHashMap<K, V>(new ImHashMapEntry<K, V>(hash, key, value),
                            new ImHashMap<K, V>(Entry), new ImHashMap<K, V>(Right.Entry), 2);

                    return new ImHashMap<K, V>(Right.Entry,
                        new ImHashMap<K, V>(Entry), new ImHashMap<K, V>(hash, key, value), 2);
                }

                var right = Right.AddOrKeepLeftOrRight(hash, key, value);
                if (ReferenceEquals(right, Right))
                    return this;

                return right.Height > Left.Height + 1
                    ? BalanceNewRightTree(right)
                    : new ImHashMap<K, V>(Entry, Left, right);
            }
        }

        /// Adds a new value for the specified key or keeps the existing map if the key is already in the map.
        [MethodImpl((MethodImplOptions)256)]
        public ImHashMap<K, V> AddOrKeep(int hash, K key) =>
            Height == 0 ? new ImHashMap<K, V>(hash, key)
            : hash == Hash ? KeepValueOrAddConflict(hash, key)
            : AddOrKeepLeftOrRight(hash, key);

        /// Adds a new value for the specified key or keeps the existing map if the key is already in the map.
        [MethodImpl((MethodImplOptions)256)]
        public ImHashMap<K, V> AddOrKeep(K key) =>
            AddOrKeep(key.GetHashCode(), key);

        private ImHashMap<K, V> KeepValueOrAddConflict(int hash, K key)
        {
            var conflictsData = Entry as ImHashMapConflicts<K, V>;
            return conflictsData == null && (ReferenceEquals(Key, key) || Key.Equals(key))
                ? this : AddOrKeepConflict(conflictsData, hash, key);
        }

        private ImHashMap<K, V> AddOrKeepConflict(ImHashMapConflicts<K, V> conflictsData, int hash, K key)
        {
            if (conflictsData == null)
                return new ImHashMap<K, V>(
                    new ImHashMapConflicts<K, V>(hash, Entry, new ImHashMapEntry<K, V>(hash, key)),
                    Left, Right, Height);

            var conflicts = conflictsData.Conflicts;
            var conflictCount = conflicts.Length;
            var conflictIndex = conflictCount - 1;
            while (conflictIndex != -1 && !key.Equals(conflicts[conflictIndex].Key))
                --conflictIndex;

            if (conflictIndex != -1)
                return this;

            // add the new conflicting value
            var newConflicts = new ImHashMapEntry<K, V>[conflictCount + 1];
            Array.Copy(conflicts, 0, newConflicts, 0, conflictCount);
            newConflicts[conflictCount] = new ImHashMapEntry<K, V>(hash, key);

            return new ImHashMap<K, V>(new ImHashMapConflicts<K, V>(hash, newConflicts), Left, Right, Height);
        }

        private ImHashMap<K, V> AddOrKeepLeftOrRight(int hash, K key)
        {
            if (hash < Hash)
            {
                if (Left.Height == 0)
                    return new ImHashMap<K, V>(Entry, new ImHashMap<K, V>(hash, key), Right, 2);

                if (Left.Hash == hash)
                {
                    var leftWithNewConflict = Left.KeepValueOrAddConflict(hash, key);
                    return ReferenceEquals(leftWithNewConflict, Left) ? this
                        : new ImHashMap<K, V>(Entry, leftWithNewConflict, Right, Height);
                }

                if (Right.Height == 0)
                {
                    if (hash < Left.Hash)
                        return new ImHashMap<K, V>(Left.Entry,
                            new ImHashMap<K, V>(hash, key), new ImHashMap<K, V>(Entry), 2);

                    return new ImHashMap<K, V>(new ImHashMapEntry<K, V>(hash, key),
                        new ImHashMap<K, V>(Left.Entry), new ImHashMap<K, V>(Entry), 2);
                }

                var left = Left.AddOrKeepLeftOrRight(hash, key);
                if (ReferenceEquals(left, Left))
                    return this;

                return left.Height > Right.Height + 1
                    ? BalanceNewLeftTree(left)
                    : new ImHashMap<K, V>(Entry, left, Right);
            }
            else
            {
                if (Right.Height == 0)
                    return new ImHashMap<K, V>(Entry, Left, new ImHashMap<K, V>(hash, key), 2);

                if (Right.Hash == hash)
                {
                    var rightWithNewConflict = Right.KeepValueOrAddConflict(hash, key);
                    return ReferenceEquals(rightWithNewConflict, Right) ? this
                        : new ImHashMap<K, V>(Entry, Left, rightWithNewConflict, Height);
                }

                if (Left.Height == 0)
                {
                    if (hash < Right.Hash)
                        return new ImHashMap<K, V>(new ImHashMapEntry<K, V>(hash, key),
                            new ImHashMap<K, V>(Entry), new ImHashMap<K, V>(Right.Entry), 2);

                    return new ImHashMap<K, V>(Right.Entry,
                        new ImHashMap<K, V>(Entry), new ImHashMap<K, V>(hash, key), 2);
                }

                var right = Right.AddOrKeepLeftOrRight(hash, key);
                if (ReferenceEquals(right, Right))
                    return this;

                return right.Height > Left.Height + 1
                    ? BalanceNewRightTree(right)
                    : new ImHashMap<K, V>(Entry, Left, right);
            }
        }

        /// Updates the map with the new value if key is found, otherwise returns the same unchanged map.
        public ImHashMap<K, V> Update(int hash, K key, V value, Update<K, V> update = null)
        {
            if (Height == 0)
                return this;

            // No need to balance cause we not adding or removing nodes
            if (hash < Hash)
            {
                var left = Left.Update(hash, key, value, update);
                return ReferenceEquals(left, Left) ? this : new ImHashMap<K, V>(Entry, left, Right, Height);
            }

            if (hash > Hash)
            {
                var right = Right.Update(hash, key, value, update);
                return ReferenceEquals(right, Right) ? this : new ImHashMap<K, V>(Entry, Left, right, Height);
            }

            var conflictsData = Entry as ImHashMapConflicts<K, V>;
            if (conflictsData == null && (ReferenceEquals(Key, key) || Key.Equals(key)))
                return new ImHashMap<K, V>(
                    new ImHashMapEntry<K, V>(hash, key, update == null ? value : update(key, Value, value)),
                    Left, Right, Height);

            return AddOrUpdateConflict(conflictsData, hash, key, value, update, DoAddOrUpdateConflicts.Update);
        }

        /// Updates the map with the new value if key is found, otherwise returns the same unchanged map.
        [MethodImpl((MethodImplOptions)256)]
        public ImHashMap<K, V> Update(K key, V value) =>
            Update(key.GetHashCode(), key, value);

        /// Updates the map with the new value if key is found, otherwise returns the same unchanged map.
        [MethodImpl((MethodImplOptions)256)]
        public ImHashMap<K, V> Update(K key, V value, Update<V> update) =>
            Update(key.GetHashCode(), key, value, update.IgnoreKey);

        /// Updates the map with the Default (null for reference types) value if key is found, otherwise returns the same unchanged map.
        [MethodImpl((MethodImplOptions)256)]
        public ImHashMap<K, V> UpdateToDefault(int hash, K key)
        {
            if (Height == 0)
                return this;

            // No need to balance cause we not adding or removing nodes
            if (hash < Hash)
            {
                var left = Left.UpdateToDefault(hash, key);
                return left == Left ? this : new ImHashMap<K, V>(Entry, left, Right, Height);
            }

            if (hash > Hash)
            {
                var right = Right.UpdateToDefault(hash, key);
                return right == Right ? this : new ImHashMap<K, V>(Entry, Left, right, Height);
            }

            var conflictsData = Entry as ImHashMapConflicts<K, V>;
            if (conflictsData == null && (ReferenceEquals(Key, key) || Key.Equals(key)))
                return new ImHashMap<K, V>(new ImHashMapEntry<K, V>(hash, key), Left, Right, Height);

            return UpdateConflictToDefault(conflictsData, hash, key);
        }

        private ImHashMap<K, V> UpdateConflictToDefault(ImHashMapConflicts<K, V> conflictsData, int hash, K key)
        {
            if (conflictsData == null)
                return this;

            var conflicts = conflictsData.Conflicts;
            var conflictCount = conflicts.Length;
            var conflictIndex = conflictCount - 1;
            while (conflictIndex != -1 && !key.Equals(conflicts[conflictIndex].Key))
                --conflictIndex;

            if (conflictIndex == -1)
                return this;

            // update the existing conflicted value
            var newConflicts = new ImHashMapEntry<K, V>[conflictCount];
            Array.Copy(conflicts, 0, newConflicts, 0, conflictCount);
            newConflicts[conflictIndex] = new ImHashMapEntry<K, V>(hash, key);
            return new ImHashMap<K, V>(new ImHashMapConflicts<K, V>(hash, newConflicts), Left, Right, Height);
        }

        /// <summary>
        /// Depth-first in-order traversal as described in http://en.wikipedia.org/wiki/Tree_traversal
        /// The only difference is using fixed size array instead of stack for speed-up.
        /// </summary>
        public IEnumerable<ImHashMapEntry<K, V>> Enumerate()
        {
            if (Height != 0)
            {
                var parents = new ImHashMap<K, V>[Height];
                var node = this;
                var parentCount = -1;
                while (node.Height != 0 || parentCount != -1)
                {
                    if (node.Height != 0)
                    {
                        parents[++parentCount] = node;
                        node = node.Left;
                    }
                    else
                    {
                        node = parents[parentCount--];
                        if (node.Entry is ImHashMapConflicts<K, V> conflictsData)
                        {
                            var conflicts = conflictsData.Conflicts;
                            for (var i = 0; i < conflicts.Length; i++)
                                yield return conflicts[i];
                        }
                        else
                        {
                            yield return node.Entry;
                        }

                        node = node.Right;
                    }
                }
            }
        }

        /// <summary>
        /// Depth-first in-order traversal as described in http://en.wikipedia.org/wiki/Tree_traversal
        /// The only difference is using fixed size array instead of stack for speed-up.
        /// Note: By passing <paramref name="parentsStack"/> you may reuse the stack array between different method calls,
        /// but it should be at least <see cref="ImHashMap{K,V}.Height"/> length. The contents of array are not important.
        /// </summary>
        public S Fold<S>(S state, Func<ImHashMapEntry<K, V>, S, S> reduce, ImHashMap<K, V>[] parentsStack = null)
        {
            if (Height == 1 && Entry is ImHashMapConflicts<K, V> == false)
                return reduce(Entry, state);

            if (Height != 0)
            {
                parentsStack = parentsStack ?? new ImHashMap<K, V>[Height];
                var node = this;
                var parentCount = -1;
                while (node.Height != 0 || parentCount != -1)
                {
                    if (node.Height != 0)
                    {
                        parentsStack[++parentCount] = node;
                        node = node.Left;
                    }
                    else
                    {
                        node = parentsStack[parentCount--];

                        if (!(node.Entry is ImHashMapConflicts<K, V> conflicts))
                            state = reduce(node.Entry, state);
                        else
                        {
                            var conflict = conflicts.Conflicts;
                            for (var i = 0; i < conflict.Length; i++)
                                state = reduce(conflict[i], state);
                        }

                        node = node.Right;
                    }
                }
            }

            return state;
        }

        /// <summary>
        /// Depth-first in-order traversal as described in http://en.wikipedia.org/wiki/Tree_traversal
        /// The only difference is using fixed size array instead of stack for speed-up.
        /// Note: By passing <paramref name="parentsStack"/> you may reuse the stack array between different method calls,
        /// but it should be at least <see cref="ImHashMap{K,V}.Height"/> length. The contents of array are not important.
        /// </summary>
        public S Fold<S>(S state, Func<ImHashMapEntry<K, V>, int, S, S> reduce, ImHashMap<K, V>[] parentsStack = null)
        {
            if (Height == 1 && Entry is ImHashMapConflicts<K, V> == false)
                return reduce(Entry, 0, state);

            if (Height != 0)
            {
                parentsStack = parentsStack ?? new ImHashMap<K, V>[Height];
                var index = 0;
                var node = this;
                var parentCount = -1;
                while (node.Height != 0 || parentCount != -1)
                {
                    if (node.Height != 0)
                    {
                        parentsStack[++parentCount] = node;
                        node = node.Left;
                    }
                    else
                    {
                        node = parentsStack[parentCount--];

                        if (!(node.Entry is ImHashMapConflicts<K, V> conflicts))
                            state = reduce(node.Entry, index++, state);
                        else
                        {
                            var conflictData = conflicts.Conflicts;
                            for (var i = 0; i < conflictData.Length; i++)
                                state = reduce(conflictData[i], index++, state);
                        }

                        node = node.Right;
                    }
                }
            }

            return state;
        }

        /// <summary>
        /// Depth-first in-order traversal as described in http://en.wikipedia.org/wiki/Tree_traversal
        /// The only difference is using fixed size array instead of stack for speed-up.
        /// Note: By passing <paramref name="parentsStack"/> you may reuse the stack array between different method calls,
        /// but it should be at least <see cref="ImHashMap{K,V}.Height"/> length. The contents of array are not important.
        /// </summary>
        public S Visit<S>(S state, Action<ImHashMapEntry<K, V>, S> effect, ImHashMap<K, V>[] parentsStack = null)
        {
            if (Height == 1 && Entry is ImHashMapConflicts<K, V> == false)
            {
                effect(Entry, state);
            }
            else if (Height != 0)
            {
                parentsStack = parentsStack ?? new ImHashMap<K, V>[Height];
                var node = this;
                var parentCount = -1;
                while (node.Height != 0 || parentCount != -1)
                {
                    if (node.Height != 0)
                    {
                        parentsStack[++parentCount] = node;
                        node = node.Left;
                    }
                    else
                    {
                        node = parentsStack[parentCount--];

                        if (!(node.Entry is ImHashMapConflicts<K, V> conflicts))
                            effect(node.Entry, state);
                        else
                        {
                            var conflict = conflicts.Conflicts;
                            for (var i = 0; i < conflict.Length; i++)
                                effect(conflict[i], state);
                        }

                        node = node.Right;
                    }
                }
            }

            return state;
        }

        /// <summary>
        /// Depth-first in-order traversal as described in http://en.wikipedia.org/wiki/Tree_traversal
        /// The only difference is using fixed size array instead of stack for speed-up.
        /// Note: By passing <paramref name="parentsStack"/> you may reuse the stack array between different method calls,
        /// but it should be at least <see cref="ImHashMap{K,V}.Height"/> length. The contents of array are not important.
        /// </summary>
        public void Visit(Action<ImHashMapEntry<K, V>> effect, ImHashMap<K, V>[] parentsStack = null)
        {
            if (Height == 1 && Entry is ImHashMapConflicts<K, V> == false)
                effect(Entry);
            else if (Height != 0)
            {
                parentsStack = parentsStack ?? new ImHashMap<K, V>[Height];
                var node = this;
                var parentCount = -1;
                while (node.Height != 0 || parentCount != -1)
                {
                    if (node.Height != 0)
                    {
                        parentsStack[++parentCount] = node;
                        node = node.Left;
                    }
                    else
                    {
                        node = parentsStack[parentCount--];

                        if (!(node.Entry is ImHashMapConflicts<K, V> conflicts))
                            effect(node.Entry);
                        else
                        {
                            var conflict = conflicts.Conflicts;
                            for (var i = 0; i < conflict.Length; i++)
                                effect(conflict[i]);
                        }

                        node = node.Right;
                    }
                }
            }
        }

        /// <summary> Finds the first entry matching the condition, returns `null` if not found </summary>
        public ImHashMapEntry<K, V> FindFirstOrDefault(Func<ImHashMapEntry<K, V>, bool> condition, ImHashMap<K, V>[] parentsStack = null)
        {
            if (Height == 1 && Entry is ImHashMapConflicts<K, V> == false)
            {
                if (condition(Entry))
                    return Entry;
            }
            else if (Height != 0)
            {
                parentsStack = parentsStack ?? new ImHashMap<K, V>[Height];
                var node = this;
                var parentCount = -1;
                while (node.Height != 0 || parentCount != -1)
                {
                    if (node.Height != 0)
                    {
                        parentsStack[++parentCount] = node;
                        node = node.Left;
                    }
                    else
                    {
                        node = parentsStack[parentCount--];

                        if (!(node.Entry is ImHashMapConflicts<K, V> conflicts))
                        {
                            if (condition(node.Entry))
                                return node.Entry;
                        }
                        else
                        {
                            var conflictedEntries = conflicts.Conflicts;
                            for (var i = 0; i < conflictedEntries.Length; i++)
                                if (condition(conflictedEntries[i]))
                                    return conflictedEntries[i];
                        }

                        node = node.Right;
                    }
                }
            }

            return null;
        }

        /// Removes or updates value for specified key, or does nothing if the key is not found (returns the unchanged map)
        /// Based on Eric Lippert http://blogs.msdn.com/b/ericlippert/archive/2008/01/21/immutability-in-c-part-nine-academic-plus-my-avl-tree-implementation.aspx
        public ImHashMap<K, V> Remove(int hash, K key) =>
            RemoveImpl(hash, key);

        /// Removes or updates value for specified key, or does nothing if the key is not found (returns the unchanged map)
        /// Based on Eric Lippert http://blogs.msdn.com/b/ericlippert/archive/2008/01/21/immutability-in-c-part-nine-academic-plus-my-avl-tree-implementation.aspx
        [MethodImpl((MethodImplOptions)256)]
        public ImHashMap<K, V> Remove(K key) =>
            RemoveImpl(key.GetHashCode(), key);

        private ImHashMap<K, V> RemoveImpl(int hash, K key, bool ignoreKey = false)
        {
            if (Height == 0)
                return this;

            ImHashMap<K, V> result;
            if (hash == Hash) // found node
            {
                if (ignoreKey || Equals(Key, key))
                {
                    if (Height == 1) // remove node
                        return Empty;

                    if (Right.IsEmpty)
                        result = Left;
                    else if (Left.IsEmpty)
                        result = Right;
                    else
                    {
                        // we have two children, so remove the next highest node and replace this node with it.
                        var next = Right;
                        while (!next.Left.IsEmpty)
                            next = next.Left;
                        result = new ImHashMap<K, V>(next.Entry, Left, Right.RemoveImpl(next.Hash, default, ignoreKey: true));
                    }
                }
                else if (Entry is ImHashMapConflicts<K, V> conflictsData)
                    return TryRemoveConflicted(conflictsData, hash, key);
                else
                    return this; // if key is not matching and no conflicts to lookup - just return
            }
            else
                result = hash < Hash
                    ? Balance(Entry, Left.RemoveImpl(hash, key, ignoreKey), Right)
                    : Balance(Entry, Left, Right.RemoveImpl(hash, key, ignoreKey));

            return result;
        }

        /// <summary> Searches for the key in the conflicts and returns true if found </summary>
        public bool ContainsConflictedData(K key)
        {
            if (Conflicts != null)
            {
                var conflicts = Conflicts;
                for (var i = 0; i < conflicts.Length; ++i)
                    if (key.Equals(conflicts[i].Key))
                        return true;
            }
            return false;
        }

        /// <summary> Searches for the key in the node conflicts </summary>
        public ImHashMapEntry<K, V> GetConflictedEntryOrDefault(K key)
        {
            if (Conflicts != null)
            {
                var conflicts = Conflicts;
                for (var i = 0; i < conflicts.Length; ++i)
                    if (key.Equals(conflicts[i].Key))
                        return conflicts[i];
            }
            return null;
        }

        /// Searches for the key in the node conflicts
        public V GetConflictedValueOrDefault(K key, V defaultValue)
        {
            if (Conflicts != null)
            {
                var conflicts = Conflicts;
                for (var i = 0; i < conflicts.Length; ++i)
                    if (key.Equals(conflicts[i].Key))
                        return conflicts[i].Value;
            }
            return defaultValue;
        }

        /// Searches for the key in the node conflicts
        public bool TryFindConflictedValue(K key, out V value)
        {
            if (Conflicts != null)
            {
                var conflicts = Conflicts;
                for (var i = 0; i < conflicts.Length; ++i)
                    if (Equals(conflicts[i].Key, key))
                    {
                        value = conflicts[i].Value;
                        return true;
                    }
            }
            value = default;
            return false;
        }

        // todo: implement in terms of BalanceNewLeftTree | BalanceNewRightTree
        private static ImHashMap<K, V> Balance(ImHashMapEntry<K, V> entry, ImHashMap<K, V> left, ImHashMap<K, V> right)
        {
            var delta = left.Height - right.Height;
            if (delta > 1) // left is longer by 2, rotate left
            {
                var leftLeft = left.Left;
                var leftRight = left.Right;
                if (leftRight.Height > leftLeft.Height)
                {
                    // double rotation:
                    //      5     =>     5     =>     4
                    //   2     6      4     6      2     5
                    // 1   4        2   3        1   3     6
                    //    3        1
                    return new ImHashMap<K, V>(leftRight.Entry,
                        new ImHashMap<K, V>(left.Entry, leftLeft, leftRight.Left),
                        new ImHashMap<K, V>(entry, leftRight.Right, right));
                }

                // one rotation:
                //      5     =>     2
                //   2     6      1     5
                // 1   4              4   6
                return new ImHashMap<K, V>(left.Entry,
                    leftLeft, new ImHashMap<K, V>(entry, leftRight, right));
            }

            if (delta < -1)
            {
                var rightLeft = right.Left;
                var rightRight = right.Right;
                return rightLeft.Height > rightRight.Height
                    ? new ImHashMap<K, V>(rightLeft.Entry,
                        new ImHashMap<K, V>(entry, left, rightLeft.Left),
                        new ImHashMap<K, V>(right.Entry, rightLeft.Right, rightRight))
                    : new ImHashMap<K, V>(right.Entry, new ImHashMap<K, V>(entry, left, rightLeft), rightRight);
            }

            return new ImHashMap<K, V>(entry, left, right);
        }

        private ImHashMap<K, V> TryRemoveConflicted(ImHashMapConflicts<K, V> conflictsData, int hash, K key)
        {
            var conflicts = conflictsData.Conflicts;
            var index = conflicts.Length - 1;
            while (index != -1 && !conflicts[index].Key.Equals(key)) --index;
            if (index == -1) // key is not found in conflicts - just return
                return this;

            // we removing the one from the 2 items, so we can reference the remaining item directly from the map node 
            if (conflicts.Length == 2)
                return new ImHashMap<K, V>(index == 0 ? conflicts[1] : conflicts[0], Left, Right, Height);

            // copy all except the `index`ed data into shrinked conflicts
            var shrinkedConflicts = new ImHashMapEntry<K, V>[conflicts.Length - 1];
            var newIndex = 0;
            for (var i = 0; i < conflicts.Length; ++i)
                if (i != index)
                    shrinkedConflicts[newIndex++] = conflicts[i];
            return new ImHashMap<K, V>(new ImHashMapConflicts<K, V>(hash, shrinkedConflicts), Left, Right, Height);
        }
    }

    /// ImHashMap methods for faster performance
    public static class ImHashMap
    {
        internal static V IgnoreKey<K, V>(this Update<V> update, K _, V oldValue, V newValue) => update(oldValue, newValue);

        /// <summary> Looks for key in a tree and returns `true` if found. </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static bool Contains<K, V>(this ImHashMap<K, V> map, int hash, K key)
        {
            while (map.Height != 0 && map.Hash != hash)
                map = hash < map.Hash ? map.Left : map.Right;
            return map.Height != 0 && (key.Equals(map.Key) || map.ContainsConflictedData(key));
        }

        /// <summary> Looks for key in a tree and returns `true` if found. </summary>
        [MethodImpl((MethodImplOptions)256)]
        public static bool Contains<K, V>(this ImHashMap<K, V> map, K key) =>
            map.Height != 0 && map.Contains(key.GetHashCode(), key);

        /// Looks for key in a tree and returns the Data object if found or `null` otherwise.
        [MethodImpl((MethodImplOptions)256)]
        public static ImHashMapEntry<K, V> GetEntryOrDefault<K, V>(this ImHashMap<K, V> map, int hash, K key)
        {
            while (map.Height != 0 && map.Hash != hash)
                map = hash < map.Hash ? map.Left : map.Right;

            return map.Height == 0 ? null :
                key.Equals(map.Key) ? map.Entry :
                map.GetConflictedEntryOrDefault(key);
        }

        /// <summary> Looks for key in a tree and returns the Data object if found or `null` otherwise. </summary> 
        [MethodImpl((MethodImplOptions)256)]
        public static ImHashMapEntry<K, V> GetEntryOrDefault<K, V>(this ImHashMap<K, V> map, K key)
        {
            if (map.Height == 0)
                return null;

            var hash = key.GetHashCode();

            while (map.Hash != hash)
            {
                map = hash < map.Hash ? map.Left : map.Right;
                if (map.Height == 0)
                    return null;
            }

            return key.Equals(map.Key) ? map.Entry : map.GetConflictedEntryOrDefault(key);
        }

        /// Looks for key in a tree and returns the key value if found, or <paramref name="defaultValue"/> otherwise.
        [MethodImpl((MethodImplOptions)256)]
        public static V GetValueOrDefault<K, V>(this ImHashMap<K, V> map, K key, V defaultValue = default)
        {
            if (map.Height == 0)
                return defaultValue;

            var hash = key.GetHashCode();

            while (map.Hash != hash)
            {
                map = hash < map.Hash ? map.Left : map.Right;
                if (map.Height == 0)
                    return defaultValue;
            }

            return key.Equals(map.Key) ? map.Value : map.GetConflictedValueOrDefault(key, defaultValue);
        }

        /// Looks for key in a tree and returns the key value if found, or <paramref name="defaultValue"/> otherwise.
        [MethodImpl((MethodImplOptions)256)]
        public static V GetValueOrDefault<K, V>(this ImHashMap<K, V> map, int hash, K key, V defaultValue = default)
        {
            if (map.Height == 0)
                return defaultValue;

            while (map.Hash != hash)
            {
                map = hash < map.Hash ? map.Left : map.Right;
                if (map.Height == 0)
                    return defaultValue;
            }

            return key.Equals(map.Key) ? map.Value : map.GetConflictedValueOrDefault(key, defaultValue);
        }

        /// Looks for key in a tree and returns the key value if found, or <paramref name="defaultValue"/> otherwise.
        [MethodImpl((MethodImplOptions)256)]
        public static V GetValueOrDefault<V>(this ImHashMap<Type, V> map, Type key, V defaultValue = default)
        {
            if (map.Height == 0)
                return defaultValue;

            var hash = RuntimeHelpers.GetHashCode(key);
            while (hash != map.Hash)
            {
                map = hash < map.Hash ? map.Left : map.Right;
                if (map.Height == 0)
                    return defaultValue;
            }

            // we don't need to check `Height != 0` again cause in that case `key` will be `null` and `ReferenceEquals` will fail
            return ReferenceEquals(key, map.Key) ? map.Value : map.GetConflictedValueOrDefault(key, defaultValue);
        }

        /// Looks for key in a tree and returns the key value if found, or <paramref name="defaultValue"/> otherwise.
        [MethodImpl((MethodImplOptions)256)]
        public static V GetValueOrDefault<V>(this ImHashMap<Type, V> map, int hash, Type key, V defaultValue = default)
        {
            if (map.Height == 0)
                return defaultValue;

            while (hash != map.Hash)
            {
                map = hash < map.Hash ? map.Left : map.Right;
                if (map.Height == 0)
                    return defaultValue;
            }

            // we don't need to check `Height != 0` again cause in that case `key` will be `null` and `ReferenceEquals` will fail
            return ReferenceEquals(key, map.Key) ? map.Value : map.GetConflictedValueOrDefault(key, defaultValue);
        }

        /// Returns true if key is found and sets the value.
        [MethodImpl((MethodImplOptions)256)]
        public static bool TryFind<K, V>(this ImHashMap<K, V> map, K key, out V value)
        {
            if (map.Height != 0)
            {
                var hash = key.GetHashCode();

                while (hash != map.Hash && map.Height != 0)
                    map = hash < map.Hash ? map.Left : map.Right;

                if (map.Height != 0)
                {
                    if (key.Equals(map.Key))
                    {
                        value = map.Value;
                        return true;
                    }

                    return map.TryFindConflictedValue(key, out value);
                }
            }

            value = default;
            return false;
        }

        /// Returns true if key is found and sets the value.
        [MethodImpl((MethodImplOptions)256)]
        public static bool TryFind<K, V>(this ImHashMap<K, V> map, int hash, K key, out V value)
        {
            if (map.Height != 0)
            {
                while (hash != map.Hash && map.Height != 0)
                    map = hash < map.Hash ? map.Left : map.Right;

                if (map.Height != 0)
                {
                    if (key.Equals(map.Key))
                    {
                        value = map.Value;
                        return true;
                    }

                    return map.TryFindConflictedValue(key, out value);
                }
            }

            value = default;
            return false;
        }

        /// Returns true if key is found and the result value.
        [MethodImpl((MethodImplOptions)256)]
        public static bool TryFind<V>(this ImHashMap<Type, V> map, Type key, out V value)
        {
            if (map.Height != 0)
            {
                var hash = RuntimeHelpers.GetHashCode(key);
                while (hash != map.Hash && map.Height != 0)
                    map = hash < map.Hash ? map.Left : map.Right;

                if (map.Height != 0)
                {
                    // assign to `var data = ...`
                    if (ReferenceEquals(key, map.Key))
                    {
                        value = map.Value;
                        return true;
                    }

                    return map.TryFindConflictedValue(key, out value);
                }
            }

            value = default;
            return false;
        }

        /// Returns true if hash and key are found and the result value, or the false otherwise
        [MethodImpl((MethodImplOptions)256)]
        public static bool TryFind<V>(this ImHashMap<Type, V> map, int hash, Type key, out V value)
        {
            if (map.Height != 0)
            {
                while (hash != map.Hash && map.Height != 0)
                    map = hash < map.Hash ? map.Left : map.Right;

                if (map.Height != 0)
                {
                    if (ReferenceEquals(key, map.Key))
                    {
                        value = map.Value;
                        return true;
                    }

                    return map.TryFindConflictedValue(key, out value);
                }
            }

            value = default;
            return false;
        }

        /// <summary>Uses `RuntimeHelpers.GetHashCode()`</summary>
        public static ImHashMap<Type, V> AddOrUpdate<V>(this ImHashMap<Type, V> map, Type key, V value) =>
            map.AddOrUpdate(RuntimeHelpers.GetHashCode(key), key, value);
    }

    /// The array of ImHashMap slots where the key first bits are used for FAST slot location
    /// and the slot is the reference to ImHashMap that can be swapped with its updated value
    public static class ImHashMapSlots
    {
        /// Default number of slots
        public const int SLOT_COUNT_POWER_OF_TWO = 32;

        /// The default mask to partition the key to the target slot
        public const int HASH_MASK_TO_FIND_SLOT = SLOT_COUNT_POWER_OF_TWO - 1;

        /// Creates the array with the empty slots
        [MethodImpl((MethodImplOptions)256)]
        public static ImHashMap<K, V>[] CreateWithEmpty<K, V>(int slotCountPowerOfTwo = SLOT_COUNT_POWER_OF_TWO)
        {
            var slots = new ImHashMap<K, V>[slotCountPowerOfTwo];
            for (var i = 0; i < slots.Length; ++i)
                slots[i] = ImHashMap<K, V>.Empty;
            return slots;
        }

        /// Returns a new tree with added or updated value for specified key.
        [MethodImpl((MethodImplOptions)256)]
        public static void AddOrUpdate<K, V>(this ImHashMap<K, V>[] slots, int hash, K key, V value, int hashMaskToFindSlot = HASH_MASK_TO_FIND_SLOT)
        {
            ref var slot = ref slots[hash & hashMaskToFindSlot];
            var copy = slot;
            if (Interlocked.CompareExchange(ref slot, copy.AddOrUpdate(hash, key, value), copy) != copy)
                RefAddOrUpdateSlot(ref slot, hash, key, value);
        }

        /// Returns a new tree with added or updated value for specified key.
        [MethodImpl((MethodImplOptions)256)]
        public static void AddOrUpdate<K, V>(this ImHashMap<K, V>[] slots, K key, V value, int hashMaskToFindSlot = HASH_MASK_TO_FIND_SLOT) =>
            slots.AddOrUpdate(key.GetHashCode(), key, value, hashMaskToFindSlot);

        /// Updates the ref to the slot with the new version - retry if the someone changed the slot in between
        public static void RefAddOrUpdateSlot<K, V>(ref ImHashMap<K, V> slot, int hash, K key, V value) =>
            Ref.Swap(ref slot, hash, key, value, (x, h, k, v) => x.AddOrUpdate(h, k, v));

        /// Updates the value with help of `updateValue` function
        [MethodImpl((MethodImplOptions)256)]
        public static void AddOrUpdate<K, V>(this ImHashMap<K, V>[] slots, int hash, K key, V value, Update<K, V> update, int hashMaskToFindSlot = HASH_MASK_TO_FIND_SLOT)
        {
            ref var slot = ref slots[hash & hashMaskToFindSlot];
            var copy = slot;
            if (Interlocked.CompareExchange(ref slot, copy.AddOrUpdate(hash, key, value, update), copy) != copy)
                RefAddOrUpdateSlot(ref slot, hash, key, value, update);
        }

        /// Updates the value with help of `updateValue` function
        [MethodImpl((MethodImplOptions)256)]
        public static void AddOrUpdate<K, V>(this ImHashMap<K, V>[] slots, K key, V value, Update<K, V> updateValue, int hashMaskToFindSlot = HASH_MASK_TO_FIND_SLOT) =>
            slots.AddOrUpdate(key.GetHashCode(), key, value, updateValue, hashMaskToFindSlot);

        /// Update the ref to the slot with the new version - retry if the someone changed the slot in between
        public static void RefAddOrUpdateSlot<K, V>(ref ImHashMap<K, V> slot, int hash, K key, V value, Update<K, V> update) =>
            Ref.Swap(ref slot, hash, key, value, (x, h, k, v) => x.AddOrUpdate(h, k, v, update));

        /// Adds a new value for the specified key or keeps the existing map if the key is already in the map.
        [MethodImpl((MethodImplOptions)256)]
        public static void AddOrKeep<K, V>(this ImHashMap<K, V>[] slots, int hash, K key, V value, int hashMaskToFindSlot = HASH_MASK_TO_FIND_SLOT)
        {
            ref var slot = ref slots[hash & hashMaskToFindSlot];
            var copy = slot;
            if (Interlocked.CompareExchange(ref slot, copy.AddOrKeep(hash, key, value), copy) != copy)
                RefAddOrKeepSlot(ref slot, hash, key, value);
        }

        /// Adds a new value for the specified key or keeps the existing map if the key is already in the map.
        [MethodImpl((MethodImplOptions)256)]
        public static void AddOrKeep<K, V>(this ImHashMap<K, V>[] slots, K key, V value, int hashMaskToFindSlot = HASH_MASK_TO_FIND_SLOT) =>
            slots.AddOrKeep(key.GetHashCode(), key, value, hashMaskToFindSlot);

        /// Update the ref to the slot with the new version - retry if the someone changed the slot in between
        public static void RefAddOrKeepSlot<K, V>(ref ImHashMap<K, V> slot, int hash, K key, V value) =>
            Ref.Swap(ref slot, hash, key, value, (s, h, k, v) => s.AddOrKeep(h, k, v));

        /// Updates the specified slot or does not change it
        [MethodImpl((MethodImplOptions)256)]
        public static void Update<K, V>(this ImHashMap<K, V>[] slots, int hash, K key, V value, int hashMaskToFindSlot = HASH_MASK_TO_FIND_SLOT)
        {
            ref var slot = ref slots[hash & hashMaskToFindSlot];
            var copy = slot;
            if (Interlocked.CompareExchange(ref slot, copy.Update(hash, key, value), copy) != copy)
                RefUpdateSlot(ref slot, hash, key, value);
        }

        /// Updates the specified slot or does not change it
        [MethodImpl((MethodImplOptions)256)]
        public static void Update<K, V>(this ImHashMap<K, V>[] slots, K key, V value, int hashMaskToFindSlot = HASH_MASK_TO_FIND_SLOT) =>
            slots.Update(key.GetHashCode(), key, value, hashMaskToFindSlot);

        /// Update the ref to the slot with the new version - retry if the someone changed the slot in between
        public static void RefUpdateSlot<K, V>(ref ImHashMap<K, V> slot, int hash, K key, V value) =>
            Ref.Swap(ref slot, key, value, (s, k, v) => s.Update(k, v));

        /// Returns all map tree nodes without the order
        public static S Fold<K, V, S>(this ImHashMap<K, V>[] slots, S state, Func<ImHashMapEntry<K, V>, S, S> reduce)
        {
            var parentStack = ArrayTools.Empty<ImHashMap<K, V>>();
            for (var s = 0; s < slots.Length; s++)
            {
                var map = slots[s];
                var height = map.Height;
                if (height != 0)
                {
                    if (height > 1 && parentStack.Length < height)
                        parentStack = new ImHashMap<K, V>[height];
                    state = map.Fold(state, reduce, parentStack);
                }
            }

            return state;
        }
    }
}
