import sys
from collections.abc import Iterable
from pathlib import Path
from typing import TextIO

import click

from common import config
from common.cli_utils import EnumChoice, configurable, error_reporting, versioned
from fest import UnsupportedError
from fest.templates.stdlib.arith import TemplateChoice
from seplog import check_if_program_valid, find_model_for_program

# example run: seplog -d ~/Downloads/sls-benchmarks/slrd-lm -t sls --timeout 10 -q


@click.command(
    help="Find Nonstandard Models for Separation Logic",
    context_settings=dict(
        show_default=True,
        help_option_names=["-h", "--help"],
    ),
)
@versioned
@click.argument("files", type=click.File("r"), nargs=-1)
@click.option(
    "--dir",
    "-d",
    "directories",
    type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
    multiple=True,
    help="Directories to process.",
)
@click.option("--quiet/--no-quiet", "-q/-Q", default=False)
@click.option(
    "--template",
    "-t",
    "templates",
    type=EnumChoice(TemplateChoice),
    multiple=True,
    required=True,
    default=[TemplateChoice.STANDARD.name],
)
@click.option(
    "--timeout",
    type=float,
    default=config.seplog.timeouts.models[float],
    help="Timeout (in seconds)",
)
@error_reporting
@configurable
def main(
    files: tuple[TextIO, ...],
    directories: tuple[Path, ...],
    quiet: bool,
    templates: tuple[TemplateChoice, ...],
    timeout: float,
) -> None:
    failed = False
    count = 0
    valid = 0
    successes = 0
    with config.fest.timeout.override(timeout):
        for file in all_files(files, directories):
            count += 1
            try:
                code = file.read()
                if check_if_program_valid(code):
                    valid += 1
                    if quiet:
                        click.secho(".", fg="white", bold=True, nl=False)
                    else:
                        click.secho(f"{file.name}: program is valid", fg="green")
                    continue
                for template in templates:
                    model = find_model_for_program(code, template=template)
                    # model = find_model_from_catalog_for_program(code)
                    if model is not None:
                        break
            except UnsupportedError as e:
                if quiet:
                    click.secho(".", fg="yellow", bold=True, nl=False)
                else:
                    click.secho(f"{file.name}: unsupported input, {e}", fg="yellow")
                continue
            except KeyboardInterrupt:
                break
            except Exception as e:
                if not quiet:
                    click.secho(f"{file.name}: unexpected error, {e}", fg="red")
                    raise e
                model = None

            if model is None:
                failed = True
                if quiet:
                    click.secho(f"[{file.name}]", fg="red", bold=True, nl=False)
                else:
                    click.secho(f"{file.name}: no model found", fg="red")
            else:
                successes += 1
                if quiet:
                    click.secho(".", fg="green", bold=True, nl=False)
                else:
                    click.secho(file.name, fg="green", bold=True, underline=True)
                    print(model, end="")

    if quiet:
        click.echo()
        click.echo(
            f"Summary ({count} problems): {valid} problems were valid, "
            f"found models for {successes} out of {count - valid}"
        )

    if failed:
        sys.exit(-1)


def all_files(
    files: tuple[TextIO, ...], directories: tuple[Path, ...]
) -> Iterable[TextIO]:
    for file in files:
        yield file

    for directory in directories:
        for dir_file in directory.iterdir():
            with dir_file.open("r") as opened_file:
                yield opened_file


if __name__ == "__main__":
    main()
