#!/usr/bin/env python3
# Copyright © 2025 Intel Corporation
# SPDX-License-Identifier: MIT

import argparse
import copy
import ctypes
import enum
import fcntl
import json
import pathlib
import os
import re
import sys



H_TEMPLATE = """\
/*
 * Copyright © 2026 Intel Corporation
 * SPDX-License-Identifier: MIT
 *
 * Autogenerated file, do not edit!
 */

#pragma once

#ifdef __cplusplus
extern "C" {
#endif

#ifdef __cplusplus

<%def name="comma_join(l)">${', '.join([str(i) for i in l])}</%def>

struct gen_encoding_${info.name.lower()} {
   static constexpr gen_encoding_type TYPE = GEN_ENCODING_${info.name.upper()};

% for (i, r) in info.fields.items():
   FIELD(${i}, ${comma_join(r)});
% endfor
% for (i, r) in info.compact_fields.items():
   FIELD(C_${i}, ${comma_join(r)});
% endfor
% for (i, r) in info.sub_fields.items():
   SUB_FIELD(${i}, ${comma_join(r)});
% endfor

   static constexpr std::array<gen_inst_description, 128> gen_to_description =
   []() constexpr {
      std::array<gen_inst_description, 128> r;
% for (op, brw) in info.gen_op_to_brw.items():
      r[GEN_OP_${op}] = gen_inst_description(GEN_OP_${op}, ${brw});
% endfor
      return r;
   }();

   static constexpr std::array<gen_inst_description, 128> hw_to_description =
   []() constexpr {
      std::array<gen_inst_description, 128> r;
% for (op, brw) in info.gen_op_to_brw.items():
      r[${brw}] = gen_inst_description(GEN_OP_${op}, ${brw});
% endfor
      return r;
   }();
};

#endif

#ifdef __cplusplus
}
#endif
"""


class InstructionInfo:

    def __init__(self, raw_info, name):
        self.name = name
        self.imported = set()
        self.target = self.__init_with_inheritance(raw_info, name)
        assert name in self.imported

    def __init_with_inheritance(self, raw_info, name):
        assert name in raw_info
        import_name = raw_info[name].get('import')
        if import_name:
            assert import_name not in self.imported
            self.imported.add(import_name)
            base = self.__init_with_inheritance(raw_info, import_name)
        else:
            base = dict()

        def find_dicts(d):
            return set({ k for k, v in d.items() if isinstance(v, dict) })

        new_target = copy.deepcopy(raw_info[name])

        # Drop all comment items. (object key names that start with a '#')
        for d in find_dicts(new_target):
            new_target[d] = { k: v for k, v
                              in new_target[d].items()
                              if not k.startswith('#') }

        if import_name:
            needed = find_dicts(new_target) - find_dicts(base)
            base.update({ k: {} for k in needed })
            needed = find_dicts(base) - find_dicts(new_target)
            new_target.update({ k: {} for k in needed })
            # Merge imported fields, but prefer current version
            dicts = find_dicts(new_target)
            for d in dicts:
                old_only = { k: v for k, v in base[d].items()
                             if k not in new_target[d] }
                assert len(set(old_only.keys()).intersection(set(new_target[d].keys()))) == 0
                new_target[d].update(old_only)

        required_infos = ('fields', 'sub-fields', 'compact-fields',
                          'gen-op-to-brw')
        for info in required_infos:
            if info not in new_target:
                new_target[info] = {}

        # Drop all items that are None (null in json)
        for d in find_dicts(new_target):
            new_target[d] = { k: v for k, v in new_target[d].items()
                              if v is not None }

        self.imported.add(name)
        return new_target

    def __getattr__(self, name):
        dashed_name = name.replace('_', '-')
        if dashed_name in self.target:
            return self.target[dashed_name]
        raise AttributeError

    def for_mako(self):
        return self.target

class GenInstructionSources:

    def __init__(self, args):
        self.args = args
        self.raw_info = {}
        for in_json in self.args.json:
            in_json = pathlib.Path(in_json)
            assert in_json.is_file()
            self.outdir = pathlib.Path(args.output_dir)
            assert self.outdir.is_dir()
            with open(in_json, 'r',  encoding='utf8') as json_file:
                new_info = json.load(json_file)
                # Make sure there is no overlap in json input
                assert all({k not in self.raw_info for k in new_info.keys()})
                self.raw_info.update(new_info)

        import mako.template
        self.h_template = mako.template.Template(H_TEMPLATE)

    def gen(self):
        gen_count = 0
        if self.args.cpp:
            gen_count += self.gen_cpp_headers()
        if gen_count == 0:
            print("info: No sources specified to be generated")
        return gen_count > 0

    def gen_cpp_header(self, name):
        info = InstructionInfo(self.raw_info, name)
        template_input = {
            "info": info,
        }
        filename = self.outdir / f'gen_info_{name.lower()}.h'
        with open(filename, 'w', encoding='utf8') as h:
            try:
                print('Generating', filename)
                h.write(self.h_template.render(**template_input))
            except:
                import mako.exceptions
                print(mako.exceptions.text_error_template().render())
                raise
        return True

    def gen_cpp_headers(self):
        count = 0
        names = list(self.raw_info.keys())
        for name in names:
            if self.gen_cpp_header(name):
                count += 1
        return count

class GenInstructionsApp:

    def __init__(self):
        self.parse_args()
        self.gen_sources = GenInstructionSources(self.args)
        sys.exit(0 if self.gen_sources.gen() else 1)

    def parse_args(self):
        p = argparse.ArgumentParser()
        p.add_argument("-v", "--verbose", action="count", default=0,
                       help="Enable verbose output")
        p.add_argument("--json", action="append", default=[],
                       help="Input json describing the target")
        p.add_argument("--output-dir", required=True,
                       help="Output directory for generated files")
        p.add_argument("--cpp", action="store_true", default=False,
                       help="Generate C++ headers")

        a = p.parse_args()

        self.args = a
        self.verbose = a.verbose


if __name__ == "__main__":
    GenInstructionsApp()
