# RUN: %PYTHON %s | FileCheck %s

import numpy as np
from mlir.ir import *
from mlir.dialects import quant


def run(f):
    print("\nTEST:", f.__name__)
    f()
    return f


# CHECK-LABEL: TEST: test_type_hierarchy
@run
def test_type_hierarchy():
    with Context():
        i8 = IntegerType.get_signless(8)
        any = Type.parse("!quant.any<i8<-8:7>:f32>")
        uniform = Type.parse("!quant.uniform<i8<-8:7>:f32, 0.99872:127>")
        per_axis = Type.parse("!quant.uniform<i8:f32:1, {2.0e+2,0.99872:120}>")
        sub_channel = Type.parse(
            "!quant.uniform<i8:f32:{0:1, 1:2}, {{2.0:10, 3.0:20}, {4.0:30, 5.0:40}}>"
        )
        calibrated = Type.parse("!quant.calibrated<f32<-0.998:1.2321>>")

        assert not isinstance(i8, quant.QuantizedType)
        assert isinstance(any, quant.QuantizedType)
        assert isinstance(uniform, quant.QuantizedType)
        assert isinstance(per_axis, quant.QuantizedType)
        assert isinstance(sub_channel, quant.QuantizedType)
        assert isinstance(calibrated, quant.QuantizedType)

        assert isinstance(any, quant.AnyQuantizedType)
        assert isinstance(uniform, quant.UniformQuantizedType)
        assert isinstance(per_axis, quant.UniformQuantizedPerAxisType)
        assert isinstance(sub_channel, quant.UniformQuantizedSubChannelType)
        assert isinstance(calibrated, quant.CalibratedQuantizedType)

        assert not isinstance(uniform, quant.AnyQuantizedType)
        assert not isinstance(per_axis, quant.UniformQuantizedType)
        assert not isinstance(sub_channel, quant.UniformQuantizedType)
        assert not isinstance(sub_channel, quant.UniformQuantizedPerAxisType)

        generic_t = Type(uniform)
        assert type(generic_t) is Type
        r = quant.QuantizedType.cast_to_storage_type(generic_t)
        assert isinstance(r, IntegerType)


# CHECK-LABEL: TEST: test_any_quantized_type
@run
def test_any_quantized_type():
    with Context():
        i8 = IntegerType.get_signless(8)
        f32 = F32Type.get()
        any = quant.AnyQuantizedType.get(
            quant.QuantizedType.FLAG_SIGNED, i8, f32, -8, 7
        )

        # CHECK: flags: 1
        print(f"flags: {any.flags}")
        # CHECK: signed: True
        print(f"signed: {any.is_signed}")
        # CHECK: storage type: i8
        print(f"storage type: {any.storage_type}")
        # CHECK: expressed type: f32
        print(f"expressed type: {any.expressed_type}")
        # CHECK: storage min: -8
        print(f"storage min: {any.storage_type_min}")
        # CHECK: storage max: 7
        print(f"storage max: {any.storage_type_max}")
        # CHECK: storage width: 8
        print(f"storage width: {any.storage_type_integral_width}")
        # CHECK: quantized element type: !quant.any<i8<-8:7>:f32>
        print(f"quantized element type: {any.quantized_element_type}")
        # CHECK: !quant.any<i8<-8:7>:f32>
        print(any)
        assert any == Type.parse("!quant.any<i8<-8:7>:f32>")


# CHECK-LABEL: TEST: test_uniform_type
@run
def test_uniform_type():
    with Context():
        i8 = IntegerType.get_signless(8)
        f32 = F32Type.get()
        uniform = quant.UniformQuantizedType.get(
            quant.UniformQuantizedType.FLAG_SIGNED, i8, f32, 0.99872, 127, -8, 7
        )

        # CHECK: scale: 0.99872
        print(f"scale: {uniform.scale}")
        # CHECK: zero point: 127
        print(f"zero point: {uniform.zero_point}")
        # CHECK: fixed point: False
        print(f"fixed point: {uniform.is_fixed_point}")
        # CHECK: !quant.uniform<i8<-8:7>:f32, 9.987200e-01:127>
        print(uniform)
        assert uniform == Type.parse("!quant.uniform<i8<-8:7>:f32, 0.99872:127>")


# CHECK-LABEL: TEST: test_uniform_per_axis_type
@run
def test_uniform_per_axis_type():
    with Context():
        i8 = IntegerType.get_signless(8)
        f32 = F32Type.get()
        per_axis = quant.UniformQuantizedPerAxisType.get(
            quant.QuantizedType.FLAG_SIGNED,
            i8,
            f32,
            [200, 0.99872],
            [0, 120],
            quantized_dimension=1,
            storage_type_min=quant.QuantizedType.default_minimum_for_integer(
                is_signed=True, integral_width=8
            ),
            storage_type_max=quant.QuantizedType.default_maximum_for_integer(
                is_signed=True, integral_width=8
            ),
        )

        # CHECK: scales: [200.0, 0.99872]
        print(f"scales: {per_axis.scales}")
        # CHECK: zero_points: [0, 120]
        print(f"zero_points: {per_axis.zero_points}")
        # CHECK: quantized dim: 1
        print(f"quantized dim: {per_axis.quantized_dimension}")
        # CHECK: fixed point: False
        print(f"fixed point: {per_axis.is_fixed_point}")
        # CHECK: !quant.uniform<i8:f32:1, {2.000000e+02,9.987200e-01:120}>
        print(per_axis)
        assert per_axis == Type.parse("!quant.uniform<i8:f32:1, {2.0e+2,0.99872:120}>")


# CHECK-LABEL: TEST: test_uniform_sub_channel_type
@run
def test_uniform_sub_channel_type():
    with Context():
        i8 = IntegerType.get_signless(8)
        f32 = F32Type.get()
        sub_channel = quant.UniformQuantizedSubChannelType.get(
            quant.QuantizedType.FLAG_SIGNED,
            i8,
            f32,
            DenseElementsAttr.get(
                np.asarray([2.0, 3.0, 4.0, 5.0], np.float32).reshape(2, 2)
            ),
            DenseElementsAttr.get(np.asarray([10, 20, 30, 40], np.int8).reshape(2, 2)),
            [0, 1],
            [1, 2],
            storage_type_min=quant.QuantizedType.default_minimum_for_integer(
                is_signed=True, integral_width=8
            ),
            storage_type_max=quant.QuantizedType.default_maximum_for_integer(
                is_signed=True, integral_width=8
            ),
        )

        # CHECK: quantized dimensions: [0, 1]
        print(f"quantized dimensions: {sub_channel.quantized_dimensions}")
        # CHECK: block sizes: [1, 2]
        print(f"block sizes: {sub_channel.block_sizes}")
        # CHECK: scales: {{\[}}[2. 3.]
        # CHECK:               [4. 5.]]
        print(f"scales: {np.asarray(sub_channel.scales)}")
        # CHECK: zero-points: {{\[}}[10 20]
        # CHECK:                    [30 40]]
        print(f"zero-points: {np.asarray(sub_channel.zero_points)}")
        # CHECK: !quant.uniform<i8:f32:{0:1, 1:2}, {{\{}}{2.000000e+00:10, 3.000000e+00:20}, {4.000000e+00:30, 5.000000e+00:40}}>
        print(sub_channel)
        assert sub_channel == Type.parse(
            "!quant.uniform<i8:f32:{0:1, 1:2},{{2.0:10, 3.0:20}, {4.0:30, 5.0:40}}>"
        )


# CHECK-LABEL: TEST: test_calibrated_type
@run
def test_calibrated_type():
    with Context():
        f32 = F32Type.get()
        calibrated = quant.CalibratedQuantizedType.get(f32, -0.998, 1.2321)

        # CHECK: min: -0.998
        print(f"min: {calibrated.min}")
        # CHECK: max: 1.2321
        print(f"max: {calibrated.max}")
        # CHECK: !quant.calibrated<f32<-0.998:1.232100e+00>>
        print(calibrated)
        assert calibrated == Type.parse("!quant.calibrated<f32<-0.998:1.2321>>")
