import numpy as np
import matplotlib.pyplot as plt
import os
import cModel
import time
import pdfkit
import tempfile
import runSolver
import io
import importlib
#Nariše graf za matrix modele
importlib.reload(cModel)
importlib.reload(runSolver)

def getModel(solution, modelName):
    """
    Load and parse the model from the given solution dictionary using the modelName.
    """
    Q = solution[modelName]  # Retrieve the solution dictionary for the specified model
    model = cModel.model()  # Create an instance of the cModel class
    setupFile = Q['setup']  # Get the setup file path from the solution
    modelFile = Q['model']  # Get the model file path from the solution
    parameterFile = Q['parameters']  # Get the parameter file path from the solution
    print('modelFile: {} {}'.format(modelFile, os.path.isfile(modelFile)))  # Print the model file path and its existence status
    print('parameterFile: {} {}'.format(parameterFile, os.path.isfile(parameterFile)))  # Print the parameter file path and its existence status
    model.parse(modelFile, parameterFile)  # Parse the model file with the given parameters
    return model  # Return the loaded model

def mergeSolutions(seq):
    """
    Merge multiple solution dictionaries into a single dictionary.
    """
    out = {}
    for v in ['t', 'sol', 'se', 'qt', 'sOut']:  # Merge arrays from each solution
        out[v] = np.concatenate([x[v] for x in seq])
    for v in ['lut', 'lutSE', 'setup', 'model', 'parameters', 'qt', 'sOut']:  # Use data from the last solution for these keys
        out[v] = seq[-1][v]
    return out  # Return the merged dictionary

# Define paths
fh = os.path.expanduser('~')

# Define job configurations for loading
job_configs = {
    'cDiazepam': {
        'jobDir': os.path.join(fh, 'Documents', 'Sola', 'IJS', 'PBPK_public', 'cDiazepam_Matrix')
    },
    'cDiazepam1': {
        'jobDir': os.path.join(fh, 'Documents', 'Sola', 'IJS', 'PBPK_public', 'cDiazepam1_Matrix')
    },
    'cDiazepamF': {
        'jobDir': os.path.join(fh, 'Documents', 'Sola', 'IJS', 'PBPK_public', 'cDiazepamF_Matrix')
    },
    'cDiazepamB': {
        'jobDir': os.path.join(fh, 'Documents', 'Sola', 'IJS', 'PBPK_public', 'cDiazepamB_Matrix')
    }
}

# Load and merge solutions
solution = {}
for modelName, job in job_configs.items():
    seq = [runSolver.loadSolutionFromDir(job['jobDir'], True)]
    solution[modelName] = mergeSolutions(seq)

def analyze_model(model, setup):
    """
    Perform matrix operations and print results.
    """
    tscale = runSolver.getScale(setup)  # Get the time scale from the setup
    print(f'tscale={tscale}')  # Print the time scale
    model.inspect()  # Inspect the model
    
    print("***********done************")
    print(model.M(1).shape)  # Print the shape of the matrix from the model
    print(model.m)  # Print the model parameters

    nt = setup['nt']  # Number of time points
    qtmax = 1  # Maximum time (minute)
    qt = np.linspace(0, qtmax, nt)  # Generate time points
    par = 'venousInput'  # Parameter to plot

    # Plot parameters
    try:
        hw = model.get(par)  # Get the parameter from the model
        print(hw)  # Print the parameter information
        ht = [10 * hw['value'](x) for x in qt]  # Calculate parameter values over time
        plt.plot(qt / tscale, ht)  # Plot the parameter values
    except (KeyError, TypeError):  # Handle errors if the parameter is not found
        print(f'Troubles getting {par}')
        pass

    # Measure performance of matrix operations
    start_time = time.time()  # Record start time
    for i in range(100000):  # Perform matrix operations 100,000 times
        model.M(1e7)  # Call the matrix operation
    end_time = time.time()  # Record end time
    print('Time: {:.3f} s'.format(end_time - start_time))  # Print elapsed time

    fM = model.M(0)  # Get the matrix from the model
    print('Rank: {}/{}'.format(np.linalg.matrix_rank(fM), fM.shape))  # Print the rank of the matrix

    np.set_printoptions(suppress=True, precision=2, linewidth=150)  # Set NumPy print options
    print(f'{fM}')  # Print the matrix

    v, Q = np.linalg.eig(fM)  # Perform eigenvalue decomposition
    np.set_printoptions(suppress=False)  # Reset NumPy print options
    print(Q[:, 2:4])  # Print selected eigenvectors

    Q1 = np.linalg.inv(Q)  # Calculate the inverse of the eigenvector matrix
    D = np.diag(v)  # Create a diagonal matrix of eigenvalues

    t = np.linspace(0, 100, 101)  # Generate time points for plotting
    fy = [model.u(x)[14] for x in t]  # Calculate model output over time
    print('{} {}'.format(len(fy), len(t)))  # Print lengths of output and time points
    plt.figure()  # Create a new figure
    plt.plot(fy)  # Plot the model output
    fu = model.u(0)  # Get the model output at time 0
    print(Q1 @ fu)  # Print the result of the matrix multiplication

def plot_results(solution, modelName):
    """
    Plot results for the specified model from the solution.
    """
    model = getModel(solution, modelName)  # Load the model
    setup = solution[modelName]['setup']  # Get the setup from the solution
    tscale = runSolver.getScale(setup)  # Get the time scale
    print(f'tscale={tscale}')  # Print the time scale
    model.inspect()  # Inspect the model

    name = ['venous', 'adipose', 'brain', 'heart', 'kidney', 'liver', 'lung', 'muscle', 'skin', 'stomach', 'splanchnic', 'excrement']  # Compartment names
    tmax = solution[modelName]['t'][-1]  # Get the maximum time from the solution


         # Define colors and shading for models
    models = {
        'cDiazepam_Matrix': {'color': 'orange', 'shadeColor': 'red'},
        'cDiazepamF_Matrix': {'color': 'blue', 'shadeColor': 'green'},
        'cDiazepam1_Matrix': {'color': 'black', 'shadeColor': 'yellow'},
        'cDiazepamB_Matrix': {'color': 'purple', 'shadeColor': 'gold'}

    }

    fig, axs = plt.subplots(4, 3, figsize=(15, 20))  # Create a grid of subplots

    for i in range(len(name)):  # Loop over each compartment
        row = i // 3  # Determine row index
        col = i % 3  # Determine column index
        ax = axs[row, col]  # Get the subplot

        for m in models:  # Loop over each model
            fM = models[m]  # Get model configuration
            Q = solution[m]  # Get solution for the model
            v = name[i]  # Get the compartment name

            try:
                j = Q['lut'][v]  # Get the index for the compartment
            except KeyError:
                try:
                    v1 = alias[v]  # Handle alias if needed
                    j = Q['lut'][v1]  # Get the index for the alias
                except KeyError:
                    print(f'No data for {v}/{v1}')  # Print error if compartment not found
                    continue

            fy = Q['sol'][:, j]  # Get the solution for the compartment
            fe = Q['se'][:, j]  # Get the standard error for the compartment
            t = Q['t']  # Get the time points

            # Plot the mean values and confidence intervals
            ax.plot(t / tscale, fy, color=fM['color'], label=f'{m} Mean')
            ax.fill_between(t / tscale, fy - fe, fy + fe, color=fM['shadeColor'], alpha=0.1)  # Shaded area for confidence intervals
            ax.plot(t / tscale, fy - fe, color=fM['shadeColor'], linewidth=1, alpha=0.2)  # Lower bound
            ax.plot(t / tscale, fy + fe, color=fM['shadeColor'], linewidth=1, alpha=0.2)  # Upper bound

        # Find the maximum y-value including confidence intervals
        max_y_value = max(np.max(fy + fe), np.max(fy - fe))  
        ax.set_ylim([0, min(max_y_value, 10000)])  # Set y-axis limit to max value or 5500, whichever is smaller
        ax.set_xlim([0, 1.1 * tmax / tscale])  # Set x-axis limits
        ax.set_xlabel(setup['tUnit'])  # Set x-axis label
        ax.set_title(name[i])  # Set plot title
        ax.legend()  # Add legend

    output_file = os.path.join(fh, 'Documents', 'Sola', 'IJS', 'PBPK_public', 'results', 'plot.png')  # Define output file path
    plt.savefig(output_file)  # Save the plot to file
    plt.show()  # Display the plot
def main():
    """
    Main function to load data, analyze model, and plot results.
    """
    fh = os.path.expanduser('~')  # Define file path root

    # Define job configurations for loading
    job_configs = {
        'cDiazepam_Matrix': {
            'jobDir': os.path.join(fh, 'Documents', 'Sola', 'IJS', 'PBPK_public', 'cDiazepam_Matrix')
        },
        'cDiazepam1_Matrix': {
            'jobDir': os.path.join(fh, 'Documents', 'Sola', 'IJS', 'PBPK_public', 'cDiazepam1_Matrix')
        },
        'cDiazepamF_Matrix': {
            'jobDir': os.path.join(fh, 'Documents', 'Sola', 'IJS', 'PBPK_public', 'cDiazepamF_Matrix')
        },
        'cDiazepamB_Matrix': {
            'jobDir': os.path.join(fh, 'Documents', 'Sola', 'IJS', 'PBPK_public', 'cDiazepamB_Matrix')
        }
    }

    # Load and merge solutions
    solution = {}
    for modelName, job in job_configs.items():
        seq = [runSolver.loadSolutionFromDir(job['jobDir'], True)]  # Load solution from directory
        solution[modelName] = mergeSolutions(seq)  # Merge solutions
    # Capture print output
    output_stream = io.StringIO()
    analyze_model(getModel(solution, 'cDiazepamF_Matrix'), solution['cDiazepamF_Matrix']['setup'])  # Analyze the model
    analysis_results = analyze_model(getModel(solution, 'cDiazepamF_Matrix'), solution['cDiazepamF_Matrix']['setup'])
    plot_results(solution, 'cDiazepamF_Matrix')  # Plot the results

    

if __name__ == "__main__":
    main()  # Execute the main function



