import os
import pandas as pd
import subprocess
from string import Template
from typing import Callable
from .cycles_input import generate_control_file
from .cycles_read import read_output

class CyclesRunner():
    def __init__(self, *, simulations: pd.DataFrame, summary: str='summary.csv', simulation_name: Callable, control_dict: Callable, operation_template: str | None=None, operation_dict: Callable | None=None):
        self.simulations = simulations
        self.summary_file = summary
        self.operation_template = operation_template
        self.operation_dict = operation_dict
        self.simulation_name = simulation_name
        self.control_dict = control_dict


    def run(self, cycles_executable: str, *, options: str='', rm_input: bool=False, rm_output: bool=False, silence: bool=True) -> None:
        """Run Cycles simulations as defined
        """
        os.makedirs('summary', exist_ok=True)

        # Read simulation file
        first = True
        for _, row in self.simulations.iterrows():
            # Use defined simulation name
            name = self.simulation_name(row)

            print(f'{name} - ', end='')

            # Get name of operation file from defined control parameters
            operation_fn = self.control_dict(row)['operation_file']

            # Create operation file if needed
            if self.operation_template is not None and self.operation_dict is not None:
                _generate_input_from_template(self.operation_template, f'./input/{operation_fn}', self.operation_dict(row))

            # Generate control file from defined control parameters
            generate_control_file(f'./input/{name}.ctrl', self.control_dict(row))

            # Run a Cycles simulation
            if _run_cycles(cycles_executable, name, options=options, silence=silence) == 0:
                _write_summary(name, first, f'summary/{self.summary_file}')
                print('Success')
                first = False
            else:
                print('Fail')

            # Remove generated input/output files
            if rm_input is True:
                subprocess.run(
                    f'rm -fr input/{name}.ctrl input/{name}_ss.soil',
                    shell=True,
                )
                # Only remove operation files if generated by Cycles runner
                if self.operation_template is not None and self.operation_dict is not None:
                    subprocess.run(
                        f'rm -fr input/{operation_fn}',
                        shell=True,
                    )
            if rm_output is True:
                subprocess.run(
                    f'rm -fr output/{name}',
                    shell=True,
                )


def _run_cycles(cycles_executable, simulation: str, options: str, silence: bool):
    cmd = [cycles_executable, options, simulation]

    result = subprocess.run(
        cmd,
        stdout=subprocess.DEVNULL if silence is True else None,
        stderr=subprocess.DEVNULL if silence is True else None,
    )

    return result.returncode


def _write_summary(simulation: str, header: bool, summary_fn: str) -> None:
    df, _ = read_output('.', simulation, 'harvest')
    df.insert(0, 'simulation', simulation)

    with open(summary_fn, 'w' if header is True else 'a') as f:
        df.to_csv(f, header=header, index=False)


def _generate_input_from_template(template_fn: str, input_fn: str, user_dict: dict) -> None:
    with open(template_fn) as f:
        operation_file_template = Template(f.read())

    with open(input_fn, 'w') as f:
        f.write(operation_file_template.substitute(user_dict) + '\n')
