#!/usr/bin/env python
# -*- coding: utf-8 -*-

## @file main_alpha_blender.py
## @brief This program applies gamma-corrected alpha blending to overlapping projector images.
## @details
## This is for multi-projector edge blending.
## It assumes two projected images overlap horizontally and creates a smooth alpha transition
## in the overlap area so that the seam becomes visually less noticeable.
##
## Main idea:
## - A linear alpha ramp (fade-in / fade-out) is generated over the overlap region.
## - The ramp is then gamma-corrected to match how projectors actually respond to input intensity.
##   (Projectors are not linear devices; without correction, the blended region can look too dark/light.)
##
## Output:
## - The output image is saved as a PNG with an updated alpha channel.
## - Optionally, the RGB channels in the overlap region are also multiplied by the same mask
##   as a debug visualization (helps confirm the fade direction and mask shape).

import cv2
import numpy as np
from config_reader import ConfigReader


class MainAlphaBlender(object):
    """
    @brief Main controller class for executing the gamma-corrected alpha-blending pipeline.
    @details
    This class performs an end-to-end pipeline:
      - Reads configuration values (input image path, blending side, physical setup, gamma).
      - Loads a PNG image (ensuring BGRA so alpha can be modified safely).
      - Computes the overlap width in pixels based on the physical projector setup.
      - Builds an alpha mask curve (linear ramp) for the overlap region.
      - Applies gamma correction to the mask curve.
      - Multiplies the image alpha channel by the mask in the overlap region.
      - Saves a new PNG with adjusted transparency.
    """

    def __init__(self):
        """
        @brief Constructor for MainAlphaBlender.
        @details
        Initializes a ConfigReader instance to retrieve blending parameters.
        The config reader is expected to provide:
          - input image filename
          - which side of the overlap this projector corresponds to ("left" or "right")
          - physical projected image width
          - physical distance between two projectors
          - gamma value used for correction
        @see config_reader.py
        """
        self.__configReader = None
        self.__configReader = ConfigReader()

    def run(self):
        """
        @brief Executes the full alpha blending process (gamma corrected).
        @details
        steps:
        1) Load configuration parameters:
           - image_name: input image file (ideally PNG with alpha)
           - side: "left" or "right" indicating which projector image this is
           - proj_width_phys: physical width of one projected image
           - dist_phys: physical distance between projector centers (or image origins depending on setup)
           - gamma: projector gamma value (> 0)

        2) Load image using OpenCV:
           - cv2.IMREAD_UNCHANGED preserves alpha if present.
           - If the image is BGR (3 channels), it is converted to BGRA (4 channels)
             so we can write an alpha mask safely.

        3) Compute overlap ratio and overlap width (pixels):
           - overlap_ratio = 1 - (dist_phys / proj_width_phys)
             Example:
               dist_phys == proj_width_phys  -> overlap_ratio = 0 (no overlap)
               dist_phys <  proj_width_phys  -> overlap_ratio > 0 (overlap exists)
           - overlap_pixels = int(width * overlap_ratio)

        4) Generate a base linear ramp across the overlap region:
           - linear_ramp is always 0 → 1 across overlap_pixels.
           - For the left image: fade out (1 → 0) in the overlap.
           - For the right image: fade in  (0 → 1) in the overlap.

        5) Gamma correction:
           - Projector brightness response is non-linear.
           - We compensate by applying:
               mask_curve = base_curve^(1/gamma)
           - This helps produce a visually smoother blend once projected.

        6) Apply the mask to the alpha channel:
           - Convert alpha to float in [0, 1].
           - Expand mask_curve vertically to match image height.
           - Multiply only the overlap region of alpha.

        7) Save output image:
           - Writes output_left.png or output_right.png depending on side.

        Error / early-exit conditions:
        - If the input image cannot be loaded, the process stops.
        - If overlap_ratio <= 0, there is no overlap (or invalid physical config), so it stops.
        - If side is not "left" or "right", it stops.

        @return This returns None.
        @see config_reader.py
        """

        print("--- Starting Alpha Blend Process (Gamma Corrected) ---")

        # 1. Get Configuration
        ## @details Retrieve all blending parameters from ConfigReader.
        image_name = self.__configReader.getImageName()
        side = self.__configReader.getSide()
        proj_width_phys = self.__configReader.getProjectedImageWidth()
        dist_phys = self.__configReader.getDistanceBetweenProjectors()
        gamma = self.__configReader.getGamma()

        # 2. Loading Image using OpenCV
        ## @details Load the image with alpha preserved (if present).
        img = cv2.imread(image_name, cv2.IMREAD_UNCHANGED)
        if img is None:
            print(f"Error: Could not load image '{image_name}'")
            return

        # Ensure BGRA format for safe alpha manipulation
        ## @details If the image has only 3 channels (BGR), add an alpha channel.
        if img.shape[2] == 3:
            img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)

        height, width = img.shape[:2]

        # 3. Calculate Overlap Ratio and Pixels
        ## @details
        ## overlap_ratio expresses the horizontal overlap fraction of the projected image:
        ##   overlap_ratio = 1 - (distance_between_projectors / projected_image_width)
        ## If projected_image_width <= 0, overlap_ratio is forced to 0 to avoid division errors.
        overlap_ratio = 1.0 - (dist_phys / proj_width_phys) if proj_width_phys > 0 else 0.0

        ## @details If overlap_ratio <= 0, there is no meaningful overlap to blend.
        if overlap_ratio <= 0:
            print("Warning: No overlap detected.")
            return

        overlap_pixels = int(width * overlap_ratio)
        print(f"Processing '{side}' | Overlap: {overlap_pixels} pixels | Gamma: {gamma}")

        # 4. Create a linear fade mask for the overlap region
        ## @brief Linear ramp values spanning the overlap region.
        ## @details This always increases from 0 → 1 (direction is handled separately).
        linear_ramp = np.linspace(0, 1, overlap_pixels)

        # Determine mask direction based on side
        ## @details
        ## - left:  needs to fade out in the overlap => 1 → 0
        ## - right: needs to fade in  in the overlap => 0 → 1
        if side == "left":
            ## @brief Left side base curve (fade-out).
            base_curve = 1.0 - linear_ramp
        elif side == "right":
            ## @brief Right side base curve (fade-in).
            base_curve = linear_ramp
        else:
            print("Error: Side must be 'left' or 'right'")
           