# This file contains configurations for matplotlib plotting parameters
import matplotlib.pyplot as plt
import matplotlib as mpl
from matplotlib import font_manager
from matplotlib.backends.backend_pdf import PdfPages
from cycler import cycler
import numpy as np
import os

colors = cycler('color', 
                ['#4285F4', # Blue
                 '#DB4437', # Red
                 '#F4B400', # Yellow
                 '#0F9D58', # Green
                 '#904EE8', 
                 '#6B72B9', 
                 '#E377C3',
                 '#FF8800',
                 ])

config = {'font.family' : 'Arial',
          'font.size'   : 7,
          'image.resample' : False,
          'figure.dpi' : 600,
          'xtick.major.size' : 2,
          'ytick.major.size' : 2,
          'xtick.major.width' : 0.5,
          'ytick.major.width' : 0.5,
          'axes.linewidth' : 0.5,
          'lines.linewidth' : 0.5,
          'savefig.dpi' : 600,
          'savefig.transparent' : False,
          'savefig.bbox' : 'tight',
          'axes.prop_cycle' : colors,
          'scatter.edgecolors' : 'none',
          'legend.frameon' : False,
          }

font_manager.fontManager.addfont('src/utils/Arial.ttf')

mpl.rcParams.update(config)
mpl.rcParams['pdf.fonttype'] = 42

def figure(figsize=(8, 6), font_size = 7, **kwargs):
    """
    Create a figure with the given figsize and kwargs.
    figsize: tuple of (width, height) in millimeters
    """
    figsize = (figsize[0] / 25.4, figsize[1] / 25.4)
    mpl.rcParams['font.size'] = font_size
    return plt.figure(figsize=figsize, **kwargs)

def subplots(nrows=1, ncols=1, figsize=(8, 6), font_size = 7, **kwargs):
    """
    Create a figure with the given figsize and kwargs.
    figsize: tuple of (width, height) in millimeters
    """
    figsize = (figsize[0] / 25.4, figsize[1] / 25.4)
    mpl.rcParams['font.size'] = font_size
    fig, ax = plt.subplots(nrows=nrows, ncols=ncols, figsize=figsize, **kwargs)
    
    # Remove the top and right spines
    if nrows == 1 and ncols == 1:
        ax.spines['right'].set_visible(False)
        ax.spines['top'].set_visible(False)
    elif nrows == 1:
        for col in range(ncols):
            ax[col].spines['right'].set_visible(False)
            ax[col].spines['top'].set_visible(False)
    elif ncols == 1:
        for row in range(nrows):
            ax[row].spines['right'].set_visible(False)
            ax[row].spines['top'].set_visible(False)
    else:
        for row in range(nrows):
            for col in range(ncols):
                ax[row, col].spines['right'].set_visible(False)
                ax[row, col].spines['top'].set_visible(False)
    return fig, ax

def plot_histogram(
        data, 
        bins,
        plot_title,
        x_name,
        y_name,
        filename,
        data_save_path
    ):
    plt.figure(figsize=(8, 6))
    plt.hist(data, bins=bins, color='#4285F4', edgecolor='black', alpha=0.7)
    plt.title(plot_title)
    plt.xlabel(x_name)
    plt.ylabel(y_name)
    plt.tight_layout()
    plt.savefig(os.path.join(data_save_path, f"{filename}.png"), bbox_inches='tight', dpi=300)
    plt.close()

def plot_heatmap(
        plot_mat, 
        plot_title, 
        cbar_label,
        color_map,
        x_name, 
        y_name, 
        filename, 
        data_save_path
        ):
    n_rows, n_cols = plot_mat.shape
    plt.figure(figsize=(8, 6))
    plt.imshow(plot_mat, cmap=color_map, aspect='auto', vmin=0, vmax=1)
    plt.colorbar(label=cbar_label)
    plt.xlabel(x_name)
    plt.ylabel(y_name)
    plt.title(plot_title)
    
    # Show ALL ticks
    plt.xticks(range(n_cols))
    plt.yticks(range(n_rows))

    # Add text annotations
    for i in range(n_rows):
        for j in range(n_cols):
            plt.text(j, i, f'{plot_mat[i,j]:.2f}', ha='center', va='center', 
                    color='white' if plot_mat[i,j] > 0.5 else 'black', fontsize=8)
    plt.tight_layout()
    plt.savefig(os.path.join(data_save_path, f"{filename}.png"), bbox_inches='tight', dpi=300)
    plt.close()

def plot_chipseq_tracks(
        data_mat, 
        cap_list, 
        loci_list=None,
        n_caps_per_page=100,
        track_height=0.3,
        figsize_width=300,
        smooth_window=None,
        log_scale=False,
        show_percentiles=False,
        data_save_path=".",
        filename_prefix="chipseq_tracks",
        output_format="pdf"  # "pdf" for multipage PDF, "png" for separate files
    ):
    
    n_loci, n_caps = data_mat.shape
    n_pages = (n_caps + n_caps_per_page - 1) // n_caps_per_page
    
    # Create loci positions (x-axis)
    loci_positions = np.arange(n_loci)
    
    # Setup output format
    if output_format == "pdf":
        pdf_filename = os.path.join(data_save_path, f"{filename_prefix}.pdf")
        pdf_pages = PdfPages(pdf_filename)
        print(f"Creating multipage PDF: {pdf_filename}")
    
    for page in range(n_pages):
        start_cap = page * n_caps_per_page
        end_cap = min(start_cap + n_caps_per_page, n_caps)
        current_n_caps = end_cap - start_cap
        
        # Create figure
        figsize_height = current_n_caps * track_height * 25.4  # Convert to mm
        fig, axes = subplots(
            nrows=current_n_caps, 
            ncols=1, 
            figsize=(figsize_width, figsize_height),
            sharex=True
        )
        
        # Handle single subplot case
        if current_n_caps == 1:
            axes = [axes]
        
        for i, cap_idx in enumerate(range(start_cap, end_cap)):
            ax = axes[i]
            cap_name = cap_list[cap_idx]
            
            # Get signal for this cap across all loci
            signal = data_mat[:, cap_idx]
            
            # Apply smoothing if requested
            if smooth_window is not None:
                signal = np.convolve(signal, np.ones(smooth_window)/smooth_window, mode='same')
            
            # Apply log scale if requested
            if log_scale:
                signal = np.log1p(signal)  # log(1+x) to handle zeros
            
            # Plot the track - handle positive and negative values separately
            positive_mask = signal >= 0
            negative_mask = signal < 0
            
            # Plot positive values in blue (above x-axis)
            if np.any(positive_mask):
                pos_signal = np.where(positive_mask, signal, 0)
                ax.fill_between(loci_positions, 0, pos_signal, alpha=0.6, color='#4285F4')
                ax.plot(loci_positions, pos_signal, linewidth=0.3, alpha=0.8, color='#4285F4')
            
            # Plot negative values in red (below x-axis)
            if np.any(negative_mask):
                neg_signal = np.where(negative_mask, signal, 0)
                ax.fill_between(loci_positions, 0, neg_signal, alpha=0.6, color='#DB4437')
                ax.plot(loci_positions, neg_signal, linewidth=0.3, alpha=0.8, color='#DB4437')
            
            # Add percentile lines if requested
            if show_percentiles:
                p25, p50, p75 = np.percentile(signal, [25, 50, 75])
                ax.axhline(y=p25, color='gray', linestyle='--', alpha=0.5, linewidth=0.3)
                ax.axhline(y=p50, color='red', linestyle='-', alpha=0.7, linewidth=0.3)
                ax.axhline(y=p75, color='gray', linestyle='--', alpha=0.5, linewidth=0.3)
            
            # Formatting - larger font for TF labels
            ax.set_ylabel(cap_name, rotation=0, ha='right', va='center', fontsize=8, fontweight='bold')
            
            # Set y-axis limits to show both positive and negative values
            signal_min, signal_max = np.min(signal), np.max(signal)
            y_margin = 0.05 * max(abs(signal_min), abs(signal_max))
            ax.set_ylim(signal_min - y_margin, signal_max + y_margin)
            
            # Add horizontal line at y=0 for reference
            ax.axhline(y=0, color='black', linewidth=0.5, alpha=0.7)
            
            # Remove y-axis ticks to save space
            ax.set_yticks([])
            
            # Tighten spacing
            ax.margins(y=0.05)
            
            # # Add statistics text
            # stats_text = f'Mean: {np.mean(signal):.2f}\nMax: {np.max(signal):.2f}'
            # if show_percentiles:
            #     stats_text += f'\nMedian: {p50:.2f}'
            # ax.text(0.98, 0.98, stats_text, transform=ax.transAxes, 
            #        verticalalignment='top', horizontalalignment='right', fontsize=6, 
            #        bbox=dict(boxstyle='round,pad=0.3', facecolor='white', alpha=0.8))
        
        # Set x-axis label only on bottom subplot
        axes[-1].set_xlabel('Loci Index')
        axes[-1].set_xlim(0, n_loci-1)
        
        # Add title to top subplot
        title = f'ChIP-seq Tracks - Page {page+1}/{n_pages}'
        if log_scale:
            title += ' (Log Scale)'
        if smooth_window:
            title += f' (Smoothed, window={smooth_window})'
        axes[0].set_title(title)
        
        plt.tight_layout()
        
        # Save figure based on output format
        if output_format == "pdf":
            pdf_pages.savefig(fig, bbox_inches='tight', dpi=300)
            print(f"Added page {page+1}/{n_pages} to PDF")
        else:
            filename = f"{filename_prefix}_page{page+1:02d}.png"
            plt.savefig(os.path.join(data_save_path, filename), bbox_inches='tight', dpi=300)
            print(f"Saved {filename}")
        
        plt.close()
    
    # Close PDF if using PDF format
    if output_format == "pdf":
        pdf_pages.close()
        print(f"Multipage PDF saved: {pdf_filename}")
        print(f"Total pages: {n_pages}")
        print(f"Total TFs: {n_caps}")

def plot_variability(variability_results: dict, save_path: str = None, 
                     show_labels: bool = True, max_labels: int = 50):
    """Plot variability scores for genes and CAPs
    
    Args:
        variability_results: Results from ChromnitronDataset.get_most_variable()
        save_path: Path to save the plot (optional)
        show_labels: Whether to show feature names on axes
        max_labels: Maximum number of labels to show before switching to rank display
    """
    gene_scores = variability_results['gene_scores']
    gene_names = variability_results['gene_names']
    cap_scores = variability_results['cap_scores']
    cap_names = variability_results['cap_names']
    
    # Reverse arrays to show highest variability at the top
    gene_scores = gene_scores[::-1]
    gene_names = gene_names[::-1]
    cap_scores = cap_scores[::-1]
    cap_names = cap_names[::-1]
    
    # Get method info from results
    condition_used = variability_results['based_on']
    
    # Reasonable figure sizing - cap the maximum height
    max_height = 16  # Maximum height in inches
    gene_height = min(max_height, max(8, len(gene_scores) * 0.02))  # Much smaller scaling
    cap_height = min(max_height, max(8, len(cap_scores) * 0.02))
    
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, max(gene_height, cap_height)))
    
    # Plot gene variability
    bars1 = ax1.barh(range(len(gene_scores)), gene_scores, color='#4285F4')  # Blue
    ax1.set_xlabel(f'Variability Score (Variance, based on {condition_used})')
    ax1.set_title(f'Top {len(gene_scores)} Most Variable Genes')
    ax1.invert_yaxis()
    
    # Conditionally show gene labels
    if show_labels and len(gene_names) <= max_labels:
        ax1.set_yticks(range(len(gene_names)))
        ax1.set_yticklabels(gene_names, fontsize=max(6, min(10, 300/len(gene_names))))
    else:
        ax1.set_yticks([0, len(gene_names)//4, len(gene_names)//2, 3*len(gene_names)//4, len(gene_names)-1])
        ax1.set_yticklabels([f'Rank {i+1}' for i in [0, len(gene_names)//4, len(gene_names)//2, 3*len(gene_names)//4, len(gene_names)-1]])
        ax1.set_ylabel('Gene Rank (by variability)')
    
    # Add corner text box with top/bottom gene names
    if len(gene_scores) > 6:
        top_genes_text = "Top 3 genes:\n" + "\n".join([f"{i+1}. {gene_names[i]}: {gene_scores[i]:.3f}" for i in range(3)])
        bottom_genes_text = "Bottom 3 genes:\n" + "\n".join([f"{len(gene_names)+i-2}. {gene_names[i]}: {gene_scores[i]:.3f}" for i in [-3, -2, -1]])
        full_text = f"{top_genes_text}\n\n{bottom_genes_text}"
        
        ax1.text(0.02, 0.98, full_text, transform=ax1.transAxes, fontsize=8, 
                verticalalignment='top', horizontalalignment='left',
                bbox=dict(boxstyle='round,pad=0.5', facecolor='lightblue', alpha=0.8))
    
    # Plot CAP variability  
    bars2 = ax2.barh(range(len(cap_scores)), cap_scores, color='#DB4437')  # Red
    ax2.set_xlabel(f'Variability Score (Variance, based on {condition_used})')
    ax2.set_title(f'Top {len(cap_scores)} Most Variable CAPs')
    ax2.invert_yaxis()
    
    # Conditionally show CAP labels
    if show_labels and len(cap_names) <= max_labels:
        ax2.set_yticks(range(len(cap_names)))
        ax2.set_yticklabels(cap_names, fontsize=max(6, min(10, 300/len(cap_names))))
    else:
        ax2.set_yticks([0, len(cap_names)//4, len(cap_names)//2, 3*len(cap_names)//4, len(cap_names)-1])
        ax2.set_yticklabels([f'Rank {i+1}' for i in [0, len(cap_names)//4, len(cap_names)//2, 3*len(cap_names)//4, len(cap_names)-1]])
        ax2.set_ylabel('CAP Rank (by variability)')
    
    # Add corner text box with top/bottom CAP names
    if len(cap_scores) > 6:
        top_caps_text = "Top 3 CAPs:\n" + "\n".join([f"{i+1}. {cap_names[i]}: {cap_scores[i]:.3f}" for i in range(3)])
        bottom_caps_text = "Bottom 3 CAPs:\n" + "\n".join([f"{len(cap_names)+i-2}. {cap_names[i]}: {cap_scores[i]:.3f}" for i in [-3, -2, -1]])
        full_text = f"{top_caps_text}\n\n{bottom_caps_text}"
        
        ax2.text(0.02, 0.98, full_text, transform=ax2.transAxes, fontsize=8, 
                verticalalignment='top', horizontalalignment='left',
                bbox=dict(boxstyle='round,pad=0.5', facecolor='lightgreen', alpha=0.8))
    
    plt.tight_layout()
    
    if save_path:
        # Create directory if it doesn't exist
        os.makedirs(os.path.dirname(save_path), exist_ok=True)
        plt.savefig(save_path, dpi=300, bbox_inches='tight')
        plt.close()
    else:
        plt.show()