"""A fixed-point configuration to support various tensor types."""
from __future__ import absolute_import

from math import ceil
from math import log2

from .factory import AbstractFactory


# NOTE the assumption in encoding/decoding is that encoded numbers will fit
#      into signed int32
class FixedpointConfig:
    """
    Helper class containing various parameters of fixed-point precision
    tensors.
    """

    def __init__(
        self,
        scaling_base: int,
        nbits: int,
        precision_integral: int,
        precision_fractional: int,
        matmul_threshold: int,
        truncation_gap: int,
        use_noninteractive_truncation: bool,
    ) -> None:
        self.scaling_base = scaling_base
        self.nbits = nbits
        self.precision_integral = precision_integral
        self.precision_fractional = precision_fractional
        self.matmul_threshold = matmul_threshold
        self.truncation_gap = truncation_gap
        self.use_noninteractive_truncation = use_noninteractive_truncation

    @property
    def bound_single_precision(self) -> int:
        total_precision = self.precision_integral + self.precision_fractional
        return self.scaling_base ** (total_precision)

    @property
    def bound_double_precision(self) -> int:
        total_precision = self.precision_integral + 2 * self.precision_fractional
        return self.scaling_base ** (total_precision)

    @property
    def bound_intermediate_results(self) -> int:
        return self.bound_double_precision * self.matmul_threshold

    @property
    def scaling_factor(self) -> int:
        return self.scaling_base**self.precision_fractional


fixed100 = FixedpointConfig(
    scaling_base=2,
    nbits=100,
    precision_integral=14,
    precision_fractional=16,
    matmul_threshold=1024,
    truncation_gap=40,
    use_noninteractive_truncation=False,
)

fixed100_ni = FixedpointConfig(
    scaling_base=2,
    nbits=100,
    precision_integral=14,
    precision_fractional=16,
    matmul_threshold=1024,
    truncation_gap=20,
    use_noninteractive_truncation=True,
)

# TODO[Morten] make sure values in int64 configs make sense

fixed64 = FixedpointConfig(
    scaling_base=3,
    nbits=64,
    precision_integral=7,
    precision_fractional=8,
    matmul_threshold=256,
    truncation_gap=20,
    use_noninteractive_truncation=False,
)

fixed64_ni = FixedpointConfig(
    scaling_base=2,
    nbits=64,
    precision_integral=10,
    precision_fractional=13,
    matmul_threshold=256,
    truncation_gap=20,
    use_noninteractive_truncation=True,
)

fixed64_heuristic = FixedpointConfig(
    scaling_base=2,
    nbits=64,
    precision_integral=11,
    precision_fractional=16,
    matmul_threshold=256,
    truncation_gap=0,
    use_noninteractive_truncation=False,
)

fixed128_heuristic = FixedpointConfig(
    scaling_base=2,
    nbits=128,
    precision_integral=20,
    precision_fractional=32,
    matmul_threshold=256,
    truncation_gap=0,
    use_noninteractive_truncation=False,
)


def _validate_fixedpoint_config(
    config: FixedpointConfig, tensor_factory: AbstractFactory
) -> bool:
    """
    Ensure the given FixedpointConfig is compatible with the current
    tensor_factory, preventing silent errors.
    """
    no_issues = True

    check_32bit = ceil(log2(config.bound_single_precision)) > 31
    check_64bit = ceil(log2(config.bound_single_precision)) > 63
    trunc_over_mod = ceil(
        log2(config.bound_double_precision)
    ) + config.truncation_gap >= log2(tensor_factory.modulus)

    if check_32bit:
        print("WARNING: Plaintext values won't fit in 32bit tensors")
        no_issues = False

    if check_64bit:
        print("WARNING: Plaintext values won't fit in 64bit values")
        no_issues = False

    if trunc_over_mod:
        print("WARNING: Modulus is too small for truncation")
        no_issues = False

    # TODO[Morten] test for intermediate size wrt native type

    # TODO[Morten] in decoding we assume that x + bound fits within the native
    #              type of the backing tensor

    # TODO[Morten] truncation gap is statistical security for interactive
    #              truncation; write assertions

    return no_issues
