/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */

// The Cangjie API is in Beta. For details on its capabilities and limitations, please refer to the README file.

package std.unittest.prop_test

import std.collection.*
import std.math.abs

/*
 * Arbitrary is an interface that allows to generate random values of type T
 */
public interface Arbitrary<T> {
    static func arbitrary(random: RandomSource): Generator<T>
}

// this does not compile (backend error)
// extend Nothing <: Arbitrary<Nothing> {
//     public static func arbitrary(random: RandomSource): Generator<Nothing> {
//         Generators.generate<Nothing> { => throw Exception("Arbitrary<Nothing>.generate") }
//     }
// }

/*
 * Arbitrary instance for Unit: always returns ()
 */
extend Unit <: Arbitrary<Unit> {
    public static func arbitrary(_: RandomSource): Generator<Unit> {
        Generators.single(())
    }
}

/*
 * Arbitrary instance for Bool: returns true or false randomly
 */
extend Bool <: Arbitrary<Bool> {
    public static func arbitrary(random: RandomSource): Generator<Bool> {
        Generators.generate { random.suggestBool() }
    }
}

/*
 * Arbitrary instance for signed integer: has a higher probability of generating
 * border values: 0, 1, -1, Max and Min
 */
extend Int8 <: Arbitrary<Int8> {
    public static func arbitrary(random: RandomSource): Generator<Int8> {
        Generators.generate { random.suggestInt8() }
    }
}

/*
 * Arbitrary instance for signed integer: has a higher probability of generating
 * border values: 0, 1, -1, Max and Min. Short numbers are generated more frequently.
 */
extend Int16 <: Arbitrary<Int16> {
    public static func arbitrary(random: RandomSource): Generator<Int16> {
        Generators.generate { random.suggestInt16() }
    }
}

/*
 * Arbitrary instance for signed integer: has a higher probability of generating
 * border values: 0, 1, -1, Max and Min. Short numbers are generated more frequently.
 */
extend Int32 <: Arbitrary<Int32> {
    public static func arbitrary(random: RandomSource): Generator<Int32> {
        Generators.generate { random.suggestInt32() }
    }
}

/*
 * Arbitrary instance for signed integer: has a higher probability of generating
 * border values: 0, 1, -1, Max and Min. Short numbers are generated more frequently.
 */
extend Int64 <: Arbitrary<Int64> {
    public static func arbitrary(random: RandomSource): Generator<Int64> {
        Generators.generate { random.suggestInt64() }
    }
}

/*
 * Arbitrary instance for signed integer: has a higher probability of generating
 * border values: 0, 1, -1, Max and Min. Short numbers are generated more frequently.
 */
extend IntNative <: Arbitrary<IntNative> {
    public static func arbitrary(random: RandomSource): Generator<IntNative> {
        Generators.generate { random.suggestIntNative() }
    }
}

/*
 * Arbitrary instance for unsigned integer: has a higher probability of generating
 * border values: 0, 1 and Max
 */
extend UInt8 <: Arbitrary<UInt8> {
    public static func arbitrary(random: RandomSource): Generator<UInt8> {
        Generators.generate { random.suggestUInt8() }
    }
}

/*
 * Arbitrary instance for unsigned integer: has a higher probability of generating
 * border values: 0, 1 and Max. Short numbers are generated more frequently.
 */
extend UInt16 <: Arbitrary<UInt16> {
    public static func arbitrary(random: RandomSource): Generator<UInt16> {
        Generators.generate { random.suggestUInt16() }
    }
}

/*
 * Arbitrary instance for unsigned integer: has a higher probability of generating
 * border values: 0, 1 and Max
 */
extend UInt32 <: Arbitrary<UInt32> {
    public static func arbitrary(random: RandomSource): Generator<UInt32> {
        Generators.generate { random.suggestUInt32() }
    }
}

/*
 * Arbitrary instance for unsigned integer: has a higher probability of generating
 * border values: 0, 1 and Max
 */
extend UInt64 <: Arbitrary<UInt64> {
    public static func arbitrary(random: RandomSource): Generator<UInt64> {
        Generators.generate { random.suggestUInt64() }
    }
}

/*
 * Arbitrary instance for unsigned integer: has a higher probability of generating
 * border values: 0, 1 and Max. Short numbers are generated more frequently.
 */
extend UIntNative <: Arbitrary<UIntNative> {
    public static func arbitrary(random: RandomSource): Generator<UIntNative> {
        Generators.generate { random.suggestUIntNative() }
    }
}

/*
 * Arbitrary instance for float: returns random float
 */
extend Float16 <: Arbitrary<Float16> {
    public static func arbitrary(random: RandomSource): Generator<Float16> {
        Generators.generate { random.suggestFloat16() }
    }
}

/*
 * Arbitrary instance for float: returns random float
 */
extend Float32 <: Arbitrary<Float32> {
    public static func arbitrary(random: RandomSource): Generator<Float32> {
        Generators.generate { random.suggestFloat32() }
    }
}

/*
 * Arbitrary instance for float: returns random float
 */
extend Float64 <: Arbitrary<Float64> {
    public static func arbitrary(random: RandomSource): Generator<Float64> {
        Generators.generate { random.suggestFloat64() }
    }
}

/*
 * Arbitrary instance for Rune: has a higher probability to return characters from basic set,
 * otherwise can return any valid UTF-8 character
 */
extend Rune <: Arbitrary<Rune> {
    public static func arbitrary(random: RandomSource): Generator<Rune> {
        Generators.generate { random.suggestRune() }
    }
}

let SIZE_UPPER_LIMIT = 4000

func sizeGenerator(random: RandomSource): Generator<Int64> {
    func suggestSizeUpTo(max: Int64): Int64 {
        var size = random.nextInt64()
        if (size == Int64.Min) { size = max - 1 }
        return abs(size) % max
    }

    func approximatedNormalDist(sigma: Int64) {
        let t = sigma / 8
        /* ideally, we want normal distribution with mean = 0, deviation = sigma
           let's approximate it with uniform piecewise function with period t = sigma/8

           Let Q_N be the probability of getting what we want from (0, end_of_interval)
           for each interval as follows

           P(1t..2t) = Q1/1 + Q2/2 + Q3/3 + Q4/4 + Q5/5 + Q6/6 + Q7/7
               + Q8/8 + Q9/10 + Q10/12 + Q11/14 + Q12/16 + Q13/24 + Q4/32 + Q15/40
           P(2t..3t) = Q2/2 + Q3/3 + Q4/4 + Q5/5 + Q6/6 + Q7/7 + Q8/8
               + Q9/10 + Q10/12 + Q11/14 + Q12/16 + Q13/24 + Q4/32 + Q15/40
           P(3t..4t) = Q3/3 + Q4/4 + Q5/5 + Q6/6 + Q7/7 + Q8/8 + Q9/10
               + Q10/12 + Q11/14 + Q12/16 + Q13/24 + Q4/32 + Q15/40
           P(4t..5t) = Q4/4 + Q5/5 + Q6/6 + Q7/7 + Q8/8 + Q9/10
               + Q10/12 + Q11/14 + Q12/16 + Q13/24 + Q4/32 + Q15/40
           P(5t..6t) = Q5/5 + Q6/6 + Q7/7 + Q8/8 + Q9/10 + Q10/12
               + Q11/14 + Q12/16 + Q13/24 + Q4/32 + Q15/40
           P(6t..7t) = Q6/6 + Q7/7 + Q8/8 + Q9/10 + Q10/12
               + Q11/14 + Q12/16 + Q13/24 + Q4/32 + Q15/40
           P(7t..8t) = Q7/7 + Q8/8 + Q9/10 + Q10/12 + Q11/14
               + Q12/16 + Q13/24 + Q4/32 + Q15/40
           // after 1 sigma we go by 1/4 sigma = 2t
           P(8t..10t) = Q8/8 + Q9/10 + Q10/12 + Q11/14 + Q12/16 + Q13/24 + Q4/32 + Q15/40
           P(10t..12t) = Q9/5 + Q10/6 + Q11/7 + Q12/8 + Q13/12 + Q4/16 + Q15/20
           P(12t..14t) = Q10/6 + Q11/7 + Q12/8 + Q13/12 + Q4/16 + Q15/20
           P(14t..16t) = Q11/7 + Q12/8 + Q13/12 + Q4/16 + Q15/20
           // after 2 sigma we go by 1 sigma = 8t
           P(16t..24t) = Q12/8 + Q13/12 + Q4/16 + Q15/20
           P(24t..32t) = Q13/3 + Q4/4 + Q15/5
           P(32t..40t) = Q4/4 + Q15/5

           The overall logic of the above equation is the following: something from interval (a, b)
           can be acqured from any interval (0, c) containing (a, b) with probability c / (b - a)
           so every Q needs to be multiplied by the size it covers and divided by size of corresponding piece

           Why these 15 periods? Because it gives a visually good approximation.

           using normal distribution probability calculator, we find

           P(1t..2t) = 9.94%
           P(2t..3t) = 9.8%
           P(3t..4t) = 9.5%
           P(4t..5t) = 9.06%
           P(5t..6t) = 8.52%
           P(6t..7t) = 7.88%
           P(7t..8t) = 7.16%
           P(8t..10t) = 6.42%
           P(10t..12t) = 10.6%
           P(12t..14t) = 7.76%
           P(14t..16t) = 5.34%
           P(16t..24t) = 3.46%
           P(24t..32t) = 4.28%
           P(32t..40t) = 0.26%
           P(>40t) = 0.02%

           All the equations solved for each Q give us

           Q1 = 0.14
           Q2 = 0.6
           Q3 = 1.32
           Q4 = 2.16
           Q5 = 3.2
           Q6 = 4.32
           Q7 = 5.18
           Q8 = 8.96
           Q9 = 14.2
           Q10 = 14.52
           Q11 = 13.16
           Q12 = 19.12
           Q13 = 12.06
           Q14 = 0.96
           Q15 = 0.1, but we just leave the rest to max limit

           Generators.weighted requires integers, so we multiply by 50 and round to integer
         */
        Generators.weighted<Int64>(
            random,
            [
                (7, Generators.generate {suggestSizeUpTo(1 * t)}),
                (30, Generators.generate {suggestSizeUpTo(2 * t)}),
                (66, Generators.generate {suggestSizeUpTo(3 * t)}),
                (108, Generators.generate {suggestSizeUpTo(4 * t)}),
                (160, Generators.generate {suggestSizeUpTo(5 * t)}),
                (216, Generators.generate {suggestSizeUpTo(6 * t)}),
                (259, Generators.generate {suggestSizeUpTo(7 * t)}),
                (448, Generators.generate {suggestSizeUpTo(8 * t)}),
                (710, Generators.generate {suggestSizeUpTo(10 * t)}),
                (726, Generators.generate {suggestSizeUpTo(12 * t)}),
                (658, Generators.generate {suggestSizeUpTo(14 * t)}),
                (956, Generators.generate {suggestSizeUpTo(16 * t)}),
                (603, Generators.generate {suggestSizeUpTo(24 * t)}),
                (48, Generators.generate {suggestSizeUpTo(32 * t)}),
                (5, Generators.generate {suggestSizeUpTo(SIZE_UPPER_LIMIT)})
            ]
        )
    }

    const sigma = 256
    Generators.weighted<Int64>(
        random,
        [
            (5, Generators.single(0)),
            (5, Generators.single(1)),
            (90, approximatedNormalDist(sigma))
        ]
    )
}

/*
 * Arbitrary instance for Array: has a higher probability to generate empty or single-element arrays,
 * the size is random but leaned towards smaller values and not bigger than SIZE_UPPER_LIMIT
 * Elements are random according to their respective Arbitrary instances
 */
extend<T> Array<T> <: Arbitrary<Array<T>> where T <: Arbitrary<T> {
    public static func arbitrary(random: RandomSource): Generator<Array<T>> {
        let elementGenerator = T.arbitrary(random)
        let sizeGenerator = sizeGenerator(random)

        Generators.weighted<Array<T>>(
            random,
            [
                (10, Generators.single([])),
                (10, Generators.generate {[elementGenerator.next()]}),
                (80, Generators.generate {
                    var size = sizeGenerator.next()
                    Array<T>(size) { _ => elementGenerator.next() }
                })
            ]
        )
    }
}

/*
 * ArrayList instance size has same principals as for Array
 */
extend<T> ArrayList<T> <: Arbitrary<ArrayList<T>> where T <: Arbitrary<T> {
    // `Generators.mapped` not used due to possible OOM,
    // So here is copypaste of `Arbitrary<Array<T>>`
    public static func arbitrary(random: RandomSource): Generator<ArrayList<T>> {
        let elementGenerator = T.arbitrary(random)
        let sizeGenerator = sizeGenerator(random)

        Generators.weighted<ArrayList<T>>(
            random,
            [
                (10, Generators.single(ArrayList<T>([]))),
                (10, Generators.generate { ArrayList<T>([elementGenerator.next()]) }),
                (80, Generators.generate {
                    var size = sizeGenerator.next()
                    ArrayList<T>(size) { _ => elementGenerator.next() }
                })
            ]
        )
    }
}

/*
 * HashSet instance size has same principals as for Array
 */
extend<T> HashSet<T> <: Arbitrary<HashSet<T>> where T <: Arbitrary<T> {
    // `Generators.mapped` not used due to possible OOM,
    // So here is copypaste of `Arbitrary<HashMap<T, Unit>>`
    public static func arbitrary(random: RandomSource): Generator<HashSet<T>> {
        let elementGenerator = T.arbitrary(random)
        let sizeGenerator = sizeGenerator(random)

        Generators.weighted<HashSet<T>>(
            random,
            [
                (10, Generators.single(HashSet<T>())),
                (10, Generators.generate { HashSet<T>([elementGenerator.next()]) }),
                (80, Generators.generate {
                    var size = sizeGenerator.next()

                    let set = HashSet<T>()
                    for (_ in 0..size) {
                        let v = elementGenerator.next()
                        set.add(v)
                    }

                    return set
                })
            ]
        )
    }
}

/*
 * instance size has same principals as for Array
 * Keys and values are random according to their respective Arbitrary instances
 */
extend<K, V> HashMap<K, V> <: Arbitrary<HashMap<K, V>> where K <: Arbitrary<K>, V <: Arbitrary<V> {
    public static func arbitrary(random: RandomSource): Generator<HashMap<K, V>> {
        let keyGenerator = K.arbitrary(random)
        let valueGenerator = V.arbitrary(random)
        let sizeGenerator = sizeGenerator(random)

        Generators.weighted<HashMap<K, V>>(
            random,
            [
                (10, Generators.single(HashMap<K, V>())),
                (10, Generators.generate { HashMap<K, V>([(keyGenerator.next(), valueGenerator.next())]) }),
                (80, Generators.generate {
                    var size = sizeGenerator.next()

                    let hmap = HashMap<K, V>()
                    for (_ in 0..size) {
                        let k = keyGenerator.next()
                        let v = valueGenerator.next()

                        hmap[k] = v
                    }

                    return hmap
                })
            ]
        )
    }
}

/*
 * Arbitrary instance for String: has a higher probability to generate empty or single-element arrays,
 * the size is random but leaned towards smaller values and not bigger than SIZE_UPPER_LIMIT
 * Elements are random according to their respective Arbitrary instances
 */
extend String <: Arbitrary<String> {
    public static func arbitrary(random: RandomSource): Generator<String> {
        Generators.mapped<Array<Rune>, String>(random) { it: Array<Rune> => String(it) }
    }
}

/*
 * Arbitrary instance for Option: has a high probability to generate None,
 * in other cases generates values according to elements' respective Arbitrary instance
 */
extend<T> Option<T> <: Arbitrary<Option<T>> where T <: Arbitrary<T> {
    public static func arbitrary(random: RandomSource): Generator<Option<T>> {
        Generators.weighted<Option<T>>(
            random,
            [
                (30, Generators.single<Option<T>>(Option<T>.None)),
                (70, Generators.mapped<T, Option<T>>(random) { it: T => it })
            ]
        )
    }
}

/*
 * Arbitrary instance for Ordering: generates random Ordering values
 */
extend Ordering <: Arbitrary<Ordering> {
    public static func arbitrary(random: RandomSource): Generator<Ordering> {
        Generators.iterable(random, [Ordering.LT, Ordering.EQ, Ordering.GT])
    }
}

/*
 * Arbitrary instance for zero-argument functions, through wrapper struct
 * Generates functions returning arbitrary values.
 * NOTE: research whether we need to support other kinds of functions
 */
extend<R> Function0Wrapper<R> <: Arbitrary<Function0Wrapper<R>> where R <: Arbitrary<R> {
    public static func arbitrary(random: RandomSource): Generator<Function0Wrapper<R>> {
        Generators.mapped<R, Function0Wrapper<R>>(random) { it: R => Function0Wrapper { => it } }
    }
}

/*
 * Arbitrary instance for tuple (through TupleWrapper classes)
 * Generates random tuple values using random element values
 */
extend<T0, T1> TupleWrapper2<T0, T1> <: Arbitrary<TupleWrapper2<T0, T1>>
        where T0 <: Arbitrary<T0>,
              T1 <: Arbitrary<T1> {
    public static func arbitrary(random: RandomSource): Generator<TupleWrapper2<T0, T1>> {
        let gen0 = T0.arbitrary(random)
        let gen1 = T1.arbitrary(random)
        Generators.generate { TupleWrapper2((gen0.next(), gen1.next())) }
    }
}

/*
 * Arbitrary instance for tuple (through TupleWrapper classes)
 * Generates random tuple values using random element values
 */
extend<T0, T1, T2> TupleWrapper3<T0, T1, T2> <: Arbitrary<TupleWrapper3<T0, T1, T2>>
        where T0 <: Arbitrary<T0>,
              T1 <: Arbitrary<T1>,
              T2 <: Arbitrary<T2> {
    public static func arbitrary(random: RandomSource): Generator<TupleWrapper3<T0, T1, T2>> {
        let gen0 = T0.arbitrary(random)
        let gen1 = T1.arbitrary(random)
        let gen2 = T2.arbitrary(random)
        Generators.generate { TupleWrapper3((gen0.next(), gen1.next(), gen2.next())) }
    }
}

/*
 * Arbitrary instance for tuple (through TupleWrapper classes)
 * Generates random tuple values using random element values
 */
extend<T0, T1, T2, T3> TupleWrapper4<T0, T1, T2, T3> <: Arbitrary<TupleWrapper4<T0, T1, T2, T3>>
        where T0 <: Arbitrary<T0>,
              T1 <: Arbitrary<T1>,
              T2 <: Arbitrary<T2>,
              T3 <: Arbitrary<T3> {
    public static func arbitrary(random: RandomSource): Generator<TupleWrapper4<T0, T1, T2, T3>> {
        let gen0 = T0.arbitrary(random)
        let gen1 = T1.arbitrary(random)
        let gen2 = T2.arbitrary(random)
        let gen3 = T3.arbitrary(random)
        Generators.generate { TupleWrapper4((gen0.next(), gen1.next(), gen2.next(), gen3.next())) }
    }
}

/*
 * Arbitrary instance for tuple (through TupleWrapper classes)
 * Generates random tuple values using random element values
 */
extend<T0, T1, T2, T3, T4> TupleWrapper5<T0, T1, T2, T3, T4> <: Arbitrary<TupleWrapper5<T0, T1, T2, T3, T4>>
        where T0 <: Arbitrary<T0>,
              T1 <: Arbitrary<T1>,
              T2 <: Arbitrary<T2>,
              T3 <: Arbitrary<T3>,
              T4 <: Arbitrary<T4> {
    public static func arbitrary(random: RandomSource): Generator<TupleWrapper5<T0, T1, T2, T3, T4>> {
        let gen0 = T0.arbitrary(random)
        let gen1 = T1.arbitrary(random)
        let gen2 = T2.arbitrary(random)
        let gen3 = T3.arbitrary(random)
        let gen4 = T4.arbitrary(random)
        Generators.generate { TupleWrapper5((gen0.next(), gen1.next(), gen2.next(), gen3.next(), gen4.next())) }
    }
}