#!/usr/bin/env python3
# *****************************************************************************
"""                            ImgFilter Class
 
         Overview: 
 
        Author(s): Josh Sackos
          License: Licensed under the MIT License. See LICENSE file for details
         Revision: 0.0.1
    Revision Date: 12/25/2025
    Tool Versions: Python     3.13.3
                   Numpy      2.2.6
 
            Notes: None
"""
# *****************************************************************************

# ----* Native Imports *----
import sys;
import os;
import re;

# ----* Third Party Imports *----
import numpy as np;
import matplotlib.pyplot as plt;
import math;
from SackosArt.core.CoordPolar     import CoordPolar;
from SackosArt.types.MathEquation  import MathEquation;
from SackosArt.types.MathVar       import MathVar;

class ImgFilter():
    # ======================================================================
    """Description: Parent class for an image filter.

        Attributes: version : Current version of class.

          Examples: None
           Related: None
    """
    # ======================================================================

    # ++++ Class Variables ++++
    version = 0.1;

    def __init__(self, ftype, px_src, **kwargs) -> None:
        # ///////////////////////////////////////////////////////////////
        """Description: Class constructor
             Arguments: None
               Returns: None
        """
        # ///////////////////////////////////////////////////////////////
        self.pi       = math.pi;
        self.ftype    = ftype;
        self.px_src   = px_src;
        self.matrix   = np.zeros(self.px_src.shape, dtype=np.uint8);
        self.equation = self.getEquation(ftype);

        self.popMtx(**kwargs);

        return;

    def __del__(self) -> None:
        # ///////////////////////////////////////////////////////////////
        """Description: Class destructor
             Arguments: None
               Returns: None
        """
        # ///////////////////////////////////////////////////////////////

        return;

    def getEquation(self, ftype) -> str:
        # ///////////////////////////////////////////////////////////////
        """Description: Returns the equation (if any) asspcoated with
                        filter type
             Arguments: None
               Returns: None
        """
        # ///////////////////////////////////////////////////////////////
        ftypes = {
                  'sprial':'s(r,θ)=r+b*θ'
        };
        return ftypes.get(ftype, None);

    def popMtx(self, **kwargs) -> None:
        # ///////////////////////////////////////////////////////////////
        """Description: Populate filter matrix.
             Arguments: kwargs : Different for each filter type
               Returns: None
        """
        # ///////////////////////////////////////////////////////////////

        # Split into function prototype and expression
        fnProto, _, fnExpr = [x.strip() for x in self.equation.partition('=')];
        match  = re.search(r'\((.*?)\)', fnProto);
        fnVars = [v for v in match.group(1).split(',')];

        # Begin the execution namespace to preserve secruity
        # and keep track of what is a dimension vs. constant
        exec_namespace       = {var:np.asanyarray(kwargs[var]) for var in fnVars};
        dimension_list       = [d for d in exec_namespace.keys()];
#        exec_namespace['np'] = np;

        # Add constants to exec_namespace
        ref_names = list(compile(fnExpr, '<string>', 'eval').co_names);
        for param in ref_names:
            if not (param in exec_namespace):
                exec_namespace[param] = np.asanyarray(kwargs[param]);
        
        # Evaluate dimension ranges
        samples = 20;
        for idx, dim in enumerate(dimension_list):
            if len(exec_namespace[dim]) <= 1:
                raise Exception("ERROR: %s dimension is <= 1, aborting."% dim);
            range_start = exec_namespace[dim][0];
            range_stop  = exec_namespace[dim][1];
            exec_namespace[dim] = np.linspace(range_start,range_stop,samples);

        result_matrix = eval(fnExpr, {"__builtins__": {}}, exec_namespace);
        
        for key, val in exec_namespace.items():
            print(key, ' = ', val)

        print("\n\n", result_matrix);

        return result_matrix;

    def draw(self, pixel_src, pixel_t, vars=False) -> None:
        # ///////////////////////////////////////////////////////////////
        """Description: Function that 'draws' a shape, equation, etc.
                        into a matrix.
             Arguments: pixel_src : Pixel data, equation, etc., where all
                                    filter pixel data comes from.
                        pixel_t   : Pixel data types
                                      -> kernel
                                      -> ranges
                                      -> equation
                        vars      : Variables if an equation
               Returns: None
        """
        # ///////////////////////////////////////////////////////////////
        dtype = type(pixel_src);
        print(dtype);
        sys.exit();
        return;

    def get_spiral_indices(self, r_mtx, theta_mtx, img_shape):
        height, width = img_shape[:2]
        cx, cy = width // 2, height // 2
        
        # 1. Convert to Cartesian
        x = r_mtx * np.cos(theta_mtx)
        y = r_mtx * np.sin(theta_mtx)
        
        # 2. Shift to Image Center
        x_img = (x + cx).astype(int)
        y_img = (y + cy).astype(int)
        
        # 3. Boundary Clipping (Keep within the frame)
        # This prevents the "Feeble Human" IndexError
        valid = (x_img >= 0) & (x_img < width) & (y_img >= 0) & (y_img < height)
        
        return x_img[valid], y_img[valid]

# -----------------------------------------------------------------------------
#                           Application Entry Point
# -----------------------------------------------------------------------------
if __name__ == "__main__":

    filter_inst = ImgFilter();
    equation    = 's(r,θ) = r+bθ';

    r = MathVar(name='radius', range=(0,10)          , angular=False);
    b = MathVar(name='boom'  , range=(1, 1)          , angular=False);
    θ = MathVar(name='theta' , range=(0, 4.5*math.pi), angular=True );
    vars        = {
                   'r' : r,
                   'b' : b,
                   'θ' : θ
                  };
    
    filter_inst.draw(equation, pixel_t='equation', vars=vars);

    raise SystemExit(0);
