'''
Created on 24-Jul-2025

@author: bthakur
'''

import inspect
import traceback
from xv.util import UtilClass
import sympy as sp
import random
from sympy import Rational
from ._base_detailed_operator import BaseDetailedOperator

# _base_q_gen
# BaseQGen

class BaseQGen(UtilClass):
    """
    Base class for subtraction question generators (decimals or integers).
    All math output is formatted with SymPy's latex() for consistency.
    Uses only SymPy number types for easy conversion to LaTeX and symbolic math.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._gen_type = None
        self._base_detailed_operator = BaseDetailedOperator(verbose = self.verbose)

    def get_base_detailed_operator(self):
        return self._base_detailed_operator

    def get_gen_type(self):
        if self._gen_type is None:
            self._gen_type = self.__class__.__name__
        return self._gen_type

    def sp_latex(self, x):
        """
        Safely convert a value to a SymPy LaTeX string.
        Only needed if x might not be a SymPy number; otherwise just use self.sp_latex(x).
        """
        return "\\displaystyle " + sp .latex(x)



    def get_random_middle_num(self, a, b):
        """
        Returns a random number between a and b (exclusive).
        Handles int, decimal, and sympy Rational according to gen_type.
        """

        gen_type = self.get_gen_type()

        # Ensure a < b for random range
        low, high = min(a, b), max(a, b)

        if gen_type == "SubtractIntQGen":
            # Only return an int between a and b (exclusive)
            if high - low <= 1:
                return low  # no middle number possible
            return random.randint(low + 1, high - 1)

        if gen_type == "SubtractDecQGen":
            # Random float between a and b (exclusive)
            # Avoid endpoints
            if high - low <= 1e-9:
                return low
            return random.uniform(low + 1e-6, high - 1e-6)

        if gen_type == "SubtractFracQGen":
            # Random Rational between a and b (exclusive)
            # Find a common denominator or use a reasonable denominator
            d_a = a.q if hasattr(a, 'q') else 1
            d_b = b.q if hasattr(b, 'q') else 1
            denom = max(d_a, d_b, 10)
            n_low = int(Rational(low) * denom) + 1
            n_high = int(Rational(high) * denom) - 1
            if n_high <= n_low:
                return Rational(n_low, denom)
            n = random.randint(n_low, n_high)
            return Rational(n, denom)

        # Default: fallback to arithmetic mean
        return (a + b) / 2


    def dec_digits(self, x):
        """
        Returns the number of digits after the decimal point for a number x.
        Works for float, int, and string representations.
        """
        s = str(x)
        if '.' in s:
            return len(s.split('.')[-1])
        return 0

    def max_dec_digits(self, a, b):
        """
        Returns the maximum number of decimal digits in a or b.
        """
        return max(self.dec_digits(a), self.dec_digits(b))

    def cal_result(self, a, b, op):
        """
        Compute the result of a op b, where op is one of '+', '-', '*', '/', '**'.
        If self.gen_type is 'SubtractDecQGen', round to max decimal places in a or b.
        Returns the result of applying the operator.
        """

        # Compute result for the given operation
        if op == '+':
            result = a + b
        elif op == '-':
            result = a - b
        elif op == '*':
            result = a * b
        elif op == '/':
            result = a / b  # Note: will raise ZeroDivisionError if b==0
        elif op == '**':
            result = a ** b
        else:
            raise ValueError(f"Unsupported operation: {op}")

        gen_type = self.get_gen_type()

        # Only apply rounding for decimal subtraction generator
        if gen_type == "SubtractDecQGen":
            max_dec = self.max_dec_digits(a, b)
            result = round(result, max_dec)

        return result


    def number_line_svg(self, a, b, width=320, height=80):
        left_val = min(a, b)
        right_val = max(a, b)

        margin = 40
        y = height // 2

        # Show as plain text
        def as_str(val):
            try:
                from sympy import Rational
                if isinstance(val, Rational):
                    return str(val)
            except ImportError:
                pass
            return str(val)

        svg = f"""
        <svg width="{width}" height="{height}" xmlns="http://www.w3.org/2000/svg">
            <line x1="{margin}" y1="{y}" x2="{width-margin}" y2="{y}" stroke="#222" stroke-width="2"/>
            <circle cx="{margin}" cy="{y}" r="7" fill="#1976D2"/>
            <text x="{margin}" y="{y-18}" text-anchor="middle" font-size="17" fill="#1976D2">{as_str(left_val)}</text>
            <circle cx="{width-margin}" cy="{y}" r="7" fill="#C62828"/>
            <text x="{width-margin}" y="{y-18}" text-anchor="middle" font-size="17" fill="#C62828">{as_str(right_val)}</text>
        </svg>
        """
        return svg
