"""
Carnot Cycle Analysis - Performance vs Theoretical Limits

Three classes for comparing real machine performance against theoretical limits:

1. COP_Analysis       — Heat pumps (COP chauffage vs Carnot)
2. EER_Analysis       — Refrigeration machines (EER/COP froid vs Carnot)
3. Efficiency_Analysis — Heat engines (rendement vs Carnot & Curzon-Ahlborn)

Example
-------
>>> from ThermodynamicCycles.Carnot_Cycle_Analysis import COP_Analysis
>>> a = COP_Analysis()
>>> a.add_machine("R290 plancher", Tc=0, Th=35, COP=4.8)
>>> a.plot(savefig="cop.pdf")

>>> from ThermodynamicCycles.Carnot_Cycle_Analysis import EER_Analysis
>>> e = EER_Analysis()
>>> e.add_machine("Chiller R134a", Tc=-5, Th=35, EER=3.2)
>>> e.plot(savefig="eer.pdf")

>>> from ThermodynamicCycles.Carnot_Cycle_Analysis import Efficiency_Analysis
>>> m = Efficiency_Analysis()
>>> m.add_machine("Turbine vapeur", Tc=30, Th=500, eta=0.38)
>>> m.plot(savefig="eta.pdf")
"""

import numpy as np
import matplotlib.pyplot as plt


# ============================================================================
# Shared constants
# ============================================================================

MARKERS = ['s', 'o', 'D', '^', 'v', 'P', 'X', 'h', '*', 'p']
COLORS = [
    '#1565c0', '#6a1b9a', '#c62828', '#00838f', '#2e7d32',
    '#e65100', '#4e342e', '#0d47a1', '#ad1457', '#33691e',
]
ISO_COLORS = ['#d32f2f', '#f57c00', '#388e3c', '#1565c0', '#6a1b9a']


# ============================================================================
# 1. COP_Analysis — Heat Pumps
# ============================================================================

class COP_Analysis:
    """Analyze heat pump COP vs Carnot limit.

    Parameters
    ----------
    delta_T_range : tuple of float
        (min, max) temperature lift in K for the plot (default: (20, 100)).
    iso_ratios : list of float
        Exergetic efficiency ratios COP/COP_Carnot to display
        (default: [0.40, 0.50, 0.60]).
    """

    def __init__(self, delta_T_range=(20, 100), iso_ratios=None):
        self.delta_T_min, self.delta_T_max = delta_T_range
        self.iso_ratios = iso_ratios or [0.40, 0.50, 0.60]
        self.machines = []

    def add_machine(self, name, Tc, Th, COP):
        """Add a real heat pump data point.

        Parameters
        ----------
        name : str
            Machine name.
        Tc : float
            Cold source temperature (°C).
        Th : float
            Hot source temperature (°C).
        COP : float
            Measured COP in heating mode.
        """
        Tc_K = Tc + 273.15
        Th_K = Th + 273.15
        delta_T = Th - Tc
        cop_carnot = Th_K / delta_T
        ratio = COP / cop_carnot

        self.machines.append({
            'name': name, 'Tc': Tc, 'Th': Th,
            'delta_T': delta_T, 'COP': COP,
            'COP_carnot': cop_carnot, 'ratio': ratio,
        })

    @staticmethod
    def cop_carnot_curve(Tc_celsius, delta_T):
        """Carnot COP (heating) for given Tc and delta_T array."""
        Tc_K = Tc_celsius + 273.15
        dt = np.asarray(delta_T)
        return (Tc_K + dt) / dt

    def summary(self):
        """Formatted summary table."""
        lines = [
            f"{'Name':<25} {'Tc':>5} {'Th':>5} {'dT':>5} "
            f"{'COP':>6} {'COP_C':>6} {'Ratio':>6}",
            "-" * 65,
        ]
        for m in self.machines:
            lines.append(
                f"{m['name']:<25} {m['Tc']:>5.0f} {m['Th']:>5.0f} "
                f"{m['delta_T']:>5.0f} {m['COP']:>6.2f} "
                f"{m['COP_carnot']:>6.2f} {m['ratio']:>5.1%}"
            )
        return "\n".join(lines)

    def plot(self, figsize=(12, 7), savefig=None, title=None, show=True):
        """Plot COP vs delta_T with Carnot curve and iso-ratios."""
        if not self.machines:
            raise ValueError("No machines added.")

        Tc_ref = self.machines[0]['Tc']
        delta_T = np.linspace(self.delta_T_min, self.delta_T_max, 200)
        COP_c = self.cop_carnot_curve(Tc_ref, delta_T)

        fig, ax = plt.subplots(figsize=figsize)
        ax.plot(delta_T, COP_c, 'b-', linewidth=2.5,
                label=r'$COP_{Carnot}$')

        for i, ratio in enumerate(self.iso_ratios):
            ax.plot(delta_T, ratio * COP_c, '--',
                    color=ISO_COLORS[i % len(ISO_COLORS)],
                    linewidth=1.5, label=f'{ratio:.0%} Carnot')

        if len(self.iso_ratios) >= 2:
            ax.fill_between(delta_T, self.iso_ratios[-1] * COP_c,
                            COP_c, alpha=0.05, color='green')
            ax.fill_between(delta_T, self.iso_ratios[0] * COP_c,
                            self.iso_ratios[1] * COP_c,
                            alpha=0.05, color='red')

        for i, m in enumerate(self.machines):
            mk = MARKERS[i % len(MARKERS)]
            cl = COLORS[i % len(COLORS)]
            ax.plot(m['delta_T'], m['COP'], marker=mk, color=cl,
                    markersize=12, markeredgecolor='black',
                    markeredgewidth=1.2, zorder=5,
                    label=f"{m['name']} (COP={m['COP']:.1f})",
                    linestyle='None')
            ax.annotate(f"{m['name']}\n({m['ratio']:.0%} Carnot)",
                        (m['delta_T'], m['COP']),
                        textcoords='offset points', xytext=(10, 8),
                        fontsize=8, color=cl, fontweight='bold')

        ax.set_xlabel(r'$\Delta T = T_h - T_c$ (K)', fontsize=13)
        ax.set_ylabel('COP chauffage', fontsize=13)
        ax.set_title(title or f'COP Carnot et performances réelles '
                     f'(source froide $T_c$ = {Tc_ref}°C)',
                     fontsize=14, fontweight='bold')
        ax.set_xlim(self.delta_T_min, self.delta_T_max)
        ax.set_ylim(1, max(COP_c) * 1.05)
        ax.grid(True, alpha=0.3)
        ax.legend(loc='upper right', fontsize=9, framealpha=0.9)
        plt.tight_layout()
        if savefig:
            fmt = savefig.rsplit('.', 1)[-1] if '.' in savefig else 'pdf'
            plt.savefig(savefig, format=fmt, bbox_inches='tight')
        if show:
            plt.show()
        return fig, ax


# ============================================================================
# 2. EER_Analysis — Refrigeration Machines
# ============================================================================

class EER_Analysis:
    """Analyze refrigeration machine EER vs Carnot limit.

    The EER (Energy Efficiency Ratio) is the COP in cooling mode:
    EER = Q_froid / W_compresseur

    The Carnot limit for cooling is:
    EER_Carnot = Tc / (Th - Tc)

    Parameters
    ----------
    delta_T_range : tuple of float
        (min, max) temperature lift in K (default: (10, 80)).
    iso_ratios : list of float
        EER/EER_Carnot ratios (default: [0.30, 0.40, 0.50, 0.60]).
    """

    def __init__(self, delta_T_range=(10, 80), iso_ratios=None):
        self.delta_T_min, self.delta_T_max = delta_T_range
        self.iso_ratios = iso_ratios or [0.30, 0.40, 0.50, 0.60]
        self.machines = []

    def add_machine(self, name, Tc, Th, EER):
        """Add a real refrigeration machine data point.

        Parameters
        ----------
        name : str
            Machine name.
        Tc : float
            Evaporation temperature (°C).
        Th : float
            Condensation temperature (°C).
        EER : float
            Measured EER (COP froid).
        """
        Tc_K = Tc + 273.15
        Th_K = Th + 273.15
        delta_T = Th - Tc
        eer_carnot = Tc_K / delta_T
        ratio = EER / eer_carnot

        self.machines.append({
            'name': name, 'Tc': Tc, 'Th': Th,
            'delta_T': delta_T, 'EER': EER,
            'EER_carnot': eer_carnot, 'ratio': ratio,
        })

    @staticmethod
    def eer_carnot_curve(Tc_celsius, delta_T):
        """Carnot EER (cooling) for given Tc and delta_T array."""
        Tc_K = Tc_celsius + 273.15
        dt = np.asarray(delta_T)
        return Tc_K / dt

    def summary(self):
        """Formatted summary table."""
        lines = [
            f"{'Name':<25} {'Tc':>5} {'Th':>5} {'dT':>5} "
            f"{'EER':>6} {'EER_C':>6} {'Ratio':>6}",
            "-" * 65,
        ]
        for m in self.machines:
            lines.append(
                f"{m['name']:<25} {m['Tc']:>5.0f} {m['Th']:>5.0f} "
                f"{m['delta_T']:>5.0f} {m['EER']:>6.2f} "
                f"{m['EER_carnot']:>6.2f} {m['ratio']:>5.1%}"
            )
        return "\n".join(lines)

    def plot(self, figsize=(12, 7), savefig=None, title=None, show=True):
        """Plot EER vs delta_T with Carnot curve and iso-ratios."""
        if not self.machines:
            raise ValueError("No machines added.")

        Tc_ref = self.machines[0]['Tc']
        delta_T = np.linspace(self.delta_T_min, self.delta_T_max, 200)
        EER_c = self.eer_carnot_curve(Tc_ref, delta_T)

        fig, ax = plt.subplots(figsize=figsize)
        ax.plot(delta_T, EER_c, 'b-', linewidth=2.5,
                label=r'$EER_{Carnot}$')

        for i, ratio in enumerate(self.iso_ratios):
            ax.plot(delta_T, ratio * EER_c, '--',
                    color=ISO_COLORS[i % len(ISO_COLORS)],
                    linewidth=1.5, label=f'{ratio:.0%} Carnot')

        if len(self.iso_ratios) >= 2:
            ax.fill_between(delta_T, self.iso_ratios[-1] * EER_c,
                            EER_c, alpha=0.05, color='green')
            ax.fill_between(delta_T, self.iso_ratios[0] * EER_c,
                            self.iso_ratios[1] * EER_c,
                            alpha=0.05, color='red')

        for i, m in enumerate(self.machines):
            mk = MARKERS[i % len(MARKERS)]
            cl = COLORS[i % len(COLORS)]
            ax.plot(m['delta_T'], m['EER'], marker=mk, color=cl,
                    markersize=12, markeredgecolor='black',
                    markeredgewidth=1.2, zorder=5,
                    label=f"{m['name']} (EER={m['EER']:.1f})",
                    linestyle='None')
            ax.annotate(f"{m['name']}\n({m['ratio']:.0%} Carnot)",
                        (m['delta_T'], m['EER']),
                        textcoords='offset points', xytext=(10, 8),
                        fontsize=8, color=cl, fontweight='bold')

        ax.set_xlabel(r'$\Delta T = T_h - T_c$ (K)', fontsize=13)
        ax.set_ylabel('EER (COP froid)', fontsize=13)
        ax.set_title(title or f'EER Carnot et performances réelles '
                     f'(source froide $T_c$ = {Tc_ref}°C)',
                     fontsize=14, fontweight='bold')
        ax.set_xlim(self.delta_T_min, self.delta_T_max)
        ax.set_ylim(0, max(EER_c) * 1.05)
        ax.grid(True, alpha=0.3)
        ax.legend(loc='upper right', fontsize=9, framealpha=0.9)
        plt.tight_layout()
        if savefig:
            fmt = savefig.rsplit('.', 1)[-1] if '.' in savefig else 'pdf'
            plt.savefig(savefig, format=fmt, bbox_inches='tight')
        if show:
            plt.show()
        return fig, ax


# ============================================================================
# 3. Efficiency_Analysis — Heat Engines (Carnot + Curzon-Ahlborn)
# ============================================================================

class Efficiency_Analysis:
    """Analyze heat engine efficiency vs Carnot and Curzon-Ahlborn limits.

    Two theoretical limits are displayed:

    - **Carnot** (reversible, unreachable):
      eta_Carnot = 1 - Tc/Th

    - **Curzon-Ahlborn** (endoreversible, maximum power):
      eta_CA = 1 - sqrt(Tc/Th)

    The Curzon-Ahlborn efficiency represents the efficiency at maximum
    power output for an endoreversible engine — a more realistic upper
    bound than Carnot for real machines.

    Reference: Curzon, F.L. & Ahlborn, B. (1975). "Efficiency of a
    Carnot engine at maximum power output". American Journal of Physics,
    43(1), 22-24. doi:10.1119/1.10023

    Parameters
    ----------
    Th_range : tuple of float
        (min, max) hot source temperature in °C (default: (100, 800)).
    iso_ratios : list of float
        eta/eta_Carnot ratios (default: [0.30, 0.40, 0.50, 0.60]).
    """

    def __init__(self, Th_range=(100, 800), iso_ratios=None):
        self.Th_min, self.Th_max = Th_range
        self.iso_ratios = iso_ratios or [0.30, 0.40, 0.50, 0.60]
        self.machines = []

    def add_machine(self, name, Tc, Th, eta):
        """Add a real heat engine data point.

        Parameters
        ----------
        name : str
            Machine name.
        Tc : float
            Cold sink temperature (°C).
        Th : float
            Hot source temperature (°C).
        eta : float
            Measured thermal efficiency (0 to 1).
        """
        Tc_K = Tc + 273.15
        Th_K = Th + 273.15
        eta_carnot = 1 - Tc_K / Th_K
        eta_ca = 1 - np.sqrt(Tc_K / Th_K)
        ratio_carnot = eta / eta_carnot
        ratio_ca = eta / eta_ca

        self.machines.append({
            'name': name, 'Tc': Tc, 'Th': Th,
            'eta': eta,
            'eta_carnot': eta_carnot,
            'eta_CA': eta_ca,
            'ratio_carnot': ratio_carnot,
            'ratio_CA': ratio_ca,
        })

    @staticmethod
    def eta_carnot_curve(Tc_celsius, Th_array):
        """Carnot efficiency for given Tc and Th array (°C)."""
        Tc_K = Tc_celsius + 273.15
        Th_K = np.asarray(Th_array) + 273.15
        return 1 - Tc_K / Th_K

    @staticmethod
    def eta_curzon_ahlborn_curve(Tc_celsius, Th_array):
        """Curzon-Ahlborn efficiency for given Tc and Th array (°C).

        eta_CA = 1 - sqrt(Tc/Th)

        This is the efficiency at maximum power output for an
        endoreversible engine (irreversibilities only at the heat
        exchangers, internal cycle reversible).
        """
        Tc_K = Tc_celsius + 273.15
        Th_K = np.asarray(Th_array) + 273.15
        return 1 - np.sqrt(Tc_K / Th_K)

    def summary(self):
        """Formatted summary table."""
        lines = [
            f"{'Name':<25} {'Tc':>5} {'Th':>5} "
            f"{'eta':>6} {'Carnot':>7} {'C-A':>7} "
            f"{'%Carnot':>8} {'%C-A':>8}",
            "-" * 78,
        ]
        for m in self.machines:
            lines.append(
                f"{m['name']:<25} {m['Tc']:>5.0f} {m['Th']:>5.0f} "
                f"{m['eta']:>5.1%} {m['eta_carnot']:>6.1%} "
                f"{m['eta_CA']:>6.1%} "
                f"{m['ratio_carnot']:>7.1%} {m['ratio_CA']:>7.1%}"
            )
        return "\n".join(lines)

    def plot(self, figsize=(12, 7), savefig=None, title=None, show=True):
        """Plot efficiency vs Th with Carnot and Curzon-Ahlborn curves."""
        if not self.machines:
            raise ValueError("No machines added.")

        Tc_ref = self.machines[0]['Tc']
        Th = np.linspace(self.Th_min, self.Th_max, 200)
        eta_c = self.eta_carnot_curve(Tc_ref, Th)
        eta_ca = self.eta_curzon_ahlborn_curve(Tc_ref, Th)

        fig, ax = plt.subplots(figsize=figsize)

        # Carnot curve
        ax.plot(Th, eta_c * 100, 'b-', linewidth=2.5,
                label=r'$\eta_{Carnot} = 1 - T_c/T_h$')

        # Curzon-Ahlborn curve
        ax.plot(Th, eta_ca * 100, color='red', linewidth=2.5, linestyle='-.',
                label=r'$\eta_{C\text{-}A} = 1 - \sqrt{T_c/T_h}$'
                      r' (puissance max.)')

        # Iso-ratio curves (relative to Carnot)
        for i, ratio in enumerate(self.iso_ratios):
            ax.plot(Th, ratio * eta_c * 100, '--',
                    color=ISO_COLORS[i % len(ISO_COLORS)],
                    linewidth=1.2, label=f'{ratio:.0%} Carnot')

        # Fill zones
        ax.fill_between(Th, eta_ca * 100, eta_c * 100,
                         alpha=0.08, color='blue',
                         label='Zone endoréversible')
        if len(self.iso_ratios) >= 2:
            ax.fill_between(Th, self.iso_ratios[0] * eta_c * 100,
                            self.iso_ratios[1] * eta_c * 100,
                            alpha=0.05, color='red')

        # Machine points
        for i, m in enumerate(self.machines):
            mk = MARKERS[i % len(MARKERS)]
            cl = COLORS[i % len(COLORS)]
            ax.plot(m['Th'], m['eta'] * 100, marker=mk, color=cl,
                    markersize=12, markeredgecolor='black',
                    markeredgewidth=1.2, zorder=5,
                    label=f"{m['name']} ({m['eta']:.0%})",
                    linestyle='None')
            ax.annotate(
                f"{m['name']}\n"
                f"({m['ratio_carnot']:.0%} Carnot, "
                f"{m['ratio_CA']:.0%} C-A)",
                (m['Th'], m['eta'] * 100),
                textcoords='offset points', xytext=(10, -15),
                fontsize=8, color=cl, fontweight='bold')

        ax.set_xlabel(r'$T_h$ — Température source chaude (°C)', fontsize=13)
        ax.set_ylabel(r'Rendement thermique $\eta$ (%)', fontsize=13)
        ax.set_title(
            title or f'Rendement moteur thermique : Carnot, Curzon-Ahlborn '
                     f'et machines réelles ($T_c$ = {Tc_ref}°C)',
            fontsize=13, fontweight='bold')
        ax.set_xlim(self.Th_min, self.Th_max)
        ax.set_ylim(0, 100)
        ax.grid(True, alpha=0.3)
        ax.legend(loc='lower right', fontsize=9, framealpha=0.9)
        plt.tight_layout()
        if savefig:
            fmt = savefig.rsplit('.', 1)[-1] if '.' in savefig else 'pdf'
            plt.savefig(savefig, format=fmt, bbox_inches='tight')
        if show:
            plt.show()
        return fig, ax
