#! /bin/python3
################################################################################
# Copyright 2025 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
################################################################################

import argparse
import os
import sys
from tempfile import NamedTemporaryFile

from matmul import sampler as matmul_sampler
from matmul import primitive as matmul


def log(output):
    print("synthdnn: " + output)


def error(output):
    print("synthdnn: error: " + output)
    exit(1)


def write_batch_file(batch_file, samples, optional_args):
    batch_file.write("#### Auto-generated by synthdnn\n")
    batch_file.write(f"#### python3 synthdnn.py {' '.join(sys.argv[1:])}\n\n")
    for s in samples:
        batch_file.write(f"--reset {optional_args}{s.benchdnn_str()}\n")
    batch_file.flush()


def setup_collect_args(parser, req):
    parser.add_argument(
        "-b",
        "--batch-file",
        required=req,
        default=None,
        help="batch file used for the operation",
    )

    # Interface with benchdnn
    nargs = 1
    if not req:
        nargs = "?"
    parser.add_argument(
        "benchdnn", nargs=nargs, help="path to benchdnn executable"
    )
    parser.add_argument(
        "--engine", default="cpu", help="engine used for benchdnn execution"
    )
    parser.add_argument(
        "--impl",
        default=None,
        help="implementation to use in benchdnn execution",
    )
    parser.add_argument(
        "--skip-impl",
        default=None,
        help="implementation to skip in benchdnn execution",
    )
    parser.add_argument(
        "--collect",
        default="corr",
        help="benchdnn collection type, can be one of [corr, perf]",
    )
    parser.add_argument("-n", "--name", default="", help="sample name")


def setup_collect_subparser(subparsers):
    collect_parser = subparsers.add_parser(
        "collect", help="call with -h for information"
    )
    collect_parser.add_argument(
        "--subprogram_main", default=collect_main, help=argparse.SUPPRESS
    )

    setup_collect_args(collect_parser, True)


def get_optional_args(args):
    optional_args = []
    if args.impl:
        optional_args.append(f"--impl={args.impl}")
    if args.skip_impl:
        optional_args.append(f"--skip-impl={args.skip_impl}")

    if len(optional_args) > 0:
        return " ".join(optional_args) + " "

    return ""


def collect_main(args):
    # args.benchdnn may be a list depending on command line setup
    benchdnn = args.benchdnn
    if type(benchdnn) is list:
        benchdnn = benchdnn[0]

    if not os.path.exists(benchdnn):
        error(f"cannot execute {benchdnn}, no such file exists")

    if args.collect == "corr":
        benchdnn_args = f"--engine={args.engine} --matmul --mode-modifier=P {get_optional_args(args)}"
    elif args.collect == "perf":
        benchdnn_args = f"--engine={args.engine} --matmul --mode=F --cold-cache=all --perf-template=sample,{args.name},%prb%,%0Gflops%,%0Gbw% --memory-kind=usm_device --attr-scratchpad=user {get_optional_args(args)}"
        if args.name.find(",") != -1:
            error(f"sample name {args.name} contains invalid character: ,")
    else:
        error(f"unknown collection method {args.collect}")

    cmd = f"{benchdnn} {benchdnn_args} --batch={args.batch_file}"
    log(f"executing: {cmd}")
    ret = os.system(cmd)
    log("execution complete")
    if ret != 0:
        error(f"execution of {cmd} failed with return code {ret}")


def setup_matmul_subparser(subparsers):
    matmul_parser = subparsers.add_parser(
        "matmul", help="call with -h for information"
    )
    matmul_parser.add_argument(
        "--subprogram_main", default=matmul_main, help=argparse.SUPPRESS
    )

    # Data Collection shortcut
    setup_collect_args(matmul_parser, False)

    # Sampler Arguments
    matmul_parser.add_argument(
        "-l",
        "--layouts",
        default="all",
        help='stag:wtag:dtag, comma separated list of layouts or "all" for every supported layout',
    )
    matmul_parser.add_argument(
        "-m",
        "--iter-mode",
        default="zip",
        help="iteration mode, must be one of zip or product",
    )
    matmul_parser.add_argument(
        "-r",
        "--region",
        default="(1,1,1,1):(8,8192,8192,8192):(1,1,1,1)",
        help="([b_min,]m_min,n_min,k_min):([b_max,]m_max,n_max,k_max):([b_align,]m_align,n_align,k_align)",
    )
    matmul_parser.add_argument(
        "-s", "--samples", default=1000, help="number of samples to collect"
    )
    matmul_parser.add_argument(
        "-t",
        "--types",
        default="all",
        help='dt:dt:dt(optional fpmath-mode), comma separated list of type configurations or "all" for every supported type',
    )


def matmul_main(args):
    batch_file = (
        open(args.batch_file, "w+t") if args.batch_file is not None else None
    )
    if args.benchdnn is not None and batch_file == None:
        batch_file = NamedTemporaryFile("w+t")

    region = matmul_sampler.Region(args.region)
    types = matmul.Types(args.types)
    layouts = matmul.Layouts(args.layouts, region.ndims - 1)
    samples = matmul_sampler.Sampler(
        int(args.samples), args.iter_mode, types, layouts, region
    )
    if batch_file:
        log(f"generating batch file: {args.batch_file}")
        write_batch_file(batch_file, samples, get_optional_args(args))
        log(f"generation complete")
    else:
        write_batch_file(sys.stdout, samples, get_optional_args(args))

    if args.benchdnn:
        args.batch_file = batch_file.name
        collect_main(args)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(
        help="primitive targeted for data collection"
    )
    setup_collect_subparser(subparsers)
    setup_matmul_subparser(subparsers)
    args = parser.parse_args()
    args.subprogram_main(args)
