<reponame>scieloorg/scielo_migration
import argparse
import os
import csv
import json


BUILDER_CSV_FIELD_NAMES = [
    'record',
    'tag', 'subfield', 'subfield_name',
    'multi_val', 'composite',
    'field_name',
    'name', 'description',
]


class DataDictionaryBuilder:

    def __init__(self, file_path):
        self._file_path = file_path
        self._grouped_by_rec_and_field = None

    def _read(self):
        with open(self._file_path, newline='') as csvfile:
            reader = csv.DictReader(
                csvfile, delimiter=',', fieldnames=BUILDER_CSV_FIELD_NAMES)
            for row in reader:
                yield row

    def _group_by_rec_and_field(self):
        recs = {}
        for row in self._read():
            rec_type = row["record"]
            tag_number = row["tag"]
            tag = "v" + tag_number.zfill(3)

            field = row["field_name"]
            recs[rec_type] = recs.get(rec_type) or {}
            recs[rec_type][field] = recs[rec_type].get(field) or {}
            if not recs[rec_type][field]:
                recs[rec_type][field]["field_name"] = row.get("field_name") or ""
                recs[rec_type][field]["subfields"] = {}

            if row["subfield"]:
                recs[rec_type][field]["subfields"].update(
                    {row["subfield"]: row["subfield_name"]}
                )
            for l in ("multi_val", "composite"):
                # assume que o campo composto tender a ser
                # também multivalorado (nem sempre é)
                recs[rec_type][field][f"is_{l}"] = bool(
                    recs[rec_type][field].get(l) or
                    recs[rec_type][field]["subfields"] or
                    row.get(l) or
                    row["subfield"] or
                    row["subfield_name"]
                )
            recs[rec_type][field]["tag"] = tag
            recs[rec_type][field]["name"] = row.get("name") or ""
            recs[rec_type][field]["description"] = row.get("description") or ""
        self._grouped_by_rec_and_field = recs

    @property
    def data_dictionary(self):
        if not self._grouped_by_rec_and_field:
            self._group_by_rec_and_field()
        return self._grouped_by_rec_and_field or {}

    def get_record_data_dictionary(self, rec_type):
        if not self._grouped_by_rec_and_field:
            self._group_by_rec_and_field()
        return self._grouped_by_rec_and_field.get(rec_type) or {}

    def save(self, output_json_file_path):
        with open(output_json_file_path, "w") as fp:
            fp.write(json.dumps(self.data_dictionary, indent=2))


class ModelBuilder:

    def __init__(self, class_name, data_dictionary):
        self._class_name = class_name
        self._data_dictionary = data_dictionary

    def create_base_module(self, class_file_path):
        with open(class_file_path, "w") as fp:
            fp.write("# generated by ModelBuilder\n")
            fp.write("from scielo_migration.iid2json.meta_record import MetaRecord\n\n\n")

    def create_main_module(self, class_file_path, base_module_name):
        base_class_name = f"Base{self._class_name}"
        class_code = _class_init_builder(f"{self._class_name}", f"{base_class_name}")
        content = (
            "# generated by ModelBuilder",
            f"from scielo_migration.isisdb.{base_module_name} import {base_class_name}\n",
            "",
            f"{class_code}",
        )
        with open(class_file_path, "w") as fp:
            fp.write("\n".join(content))

    def add_class(self, class_file_path):
        blocks = [
            _class_init_builder(f"Base{self._class_name}", "MetaRecord"),
        ]
        for group_name, tag_info in sorted(self._data_dictionary.items(), key=lambda x: x[1]['tag']):
            subfields = tag_info.get('subfields') or {}
            if subfields:
                if len(subfields) == 1 and "_" in subfields.keys() and not subfields["_"]:
                    tag_info["subfields"] = None

            tag = tag_info.get('tag')
            field_name = tag_info.get('field_name') or tag
            subfields = tag_info.get('subfields') or {}
            is_multi_val = bool(tag_info.get('is_multi_val'))

            comment = _get_comment(tag, tag_info)
            blocks.append(
                _attribute_builder(
                    field_name, tag, subfields, is_multi_val,
                    comment,
                )
            )

        with open(class_file_path, "a") as fp:
            fp.write("\n".join(blocks))
            fp.write("\n"*2)


def _get_comment(tag, tag_info):
    field_name = tag_info.get('field_name') or tag
    subfields = tag_info.get('subfields') or ''
    is_multi_val = tag_info.get('is_multi_val') or ''
    name = tag_info.get('name') or field_name
    description = tag_info.get('description') or name
    return_type = "dict" if subfields else "str"
    return_subfields = subfields and {v: "" for k, v in subfields.items()}
    rows = [
        '',
        '"""',
        f"{description}",
        f"{tag} {subfields}",
        '"""',
    ]
    comment_rows = []
    for row in rows[1:]:
        comment_rows.append(" "*8 + row.rstrip() if row else '')
    return "\n".join(comment_rows)


def _class_init_builder(class_name, parent_class_name):
    return "\n".join((
        f"""# generated by ModelBuilder""",
        f"""class {class_name}({parent_class_name}):""",
        f"""""",
        f"""    def __init__(""",
        f"""            self, record, multi_val_tags=None,""",
        f"""            data_dictionary=None):""",
        f"""        super().__init__(""",
        f"""            record, multi_val_tags, data_dictionary)""",
    ))


def _attribute_builder(attribute_name, tag, subfields, is_multi_val, comment=""):
    indent = "\n" + " "*12

    optional_params = {}
    optional_params["subfields"] = subfields or {}
    optional_params["single"] = not is_multi_val
    optional_params["simple"] = not subfields

    params = [f'"{tag}"']
    if optional_params:
        params.extend(
            [f"{name}={value}"
             for name, value in optional_params.items()])
    params = ", ".join(params)

    return "\n".join((
        "",
        f"""    # generated by ModelBuilder""",
        f"""    @property""",
        f"""    def {attribute_name}(self):""",
        f"""{comment}""",
        f"""        return self.get_field_content({params})""",
    ))


def main():
    parser = argparse.ArgumentParser(
        description="Models builder")
    subparsers = parser.add_subparsers(
        title="Commands", metavar="", dest="command")

    generate_model_parser = subparsers.add_parser(
        "generate_model",
        help=(
            "Generate model"
        )
    )

    generate_model_parser.add_argument(
        "isis_records_defs_csv_file_path",
        help=(
            "CSV file path which contains ISIS Records definitions"
        )
    )

    generate_model_parser.add_argument(
        "record_type",
        help=(
            "record type"
        )
    )

    generate_model_parser.add_argument(
        "class_name",
        help=(
            "class name"
        )
    )

    generate_model_parser.add_argument(
        "class_file_path",
        help=(
            "module file"
        )
    )
    generate_json_data_dictionary_parser = subparsers.add_parser(
        "generate_json_data_dictionary",
        help=(
            "Generate data dicionary json file"
        )
    )
    generate_json_data_dictionary_parser.add_argument(
        "isis_records_defs_csv_file_path",
        help=(
            "CSV file path which contains ISIS Records definitions"
        )
    )

    generate_json_data_dictionary_parser.add_argument(
        "data_dictionary_json_file_path",
        help=(
            "data dictionary json file path"
        )
    )

    generate_module_parser = subparsers.add_parser(
        "generate_module_py",
        help=(
            "Generate python module"
        )
    )

    generate_module_parser.add_argument(
        "data_dictionary_json_file_path",
        help=(
            "data_dictionary json file path"
        )
    )

    generate_module_parser.add_argument(
        "record_type",
        help=(
            "record type"
        )
    )

    generate_module_parser.add_argument(
        "class_name",
        help=(
            "class name"
        )
    )

    generate_module_parser.add_argument(
        "class_file_path",
        help=(
            "module file"
        )
    )

    args = parser.parse_args()
    if args.command == "generate_json_data_dictionary":
        builder = DataDictionaryBuilder(args.isis_records_defs_csv_file_path)
        builder.save(args.data_dictionary_json_file_path)
    elif args.command == "generate_module_py":
        with open(args.data_dictionary_json_file_path) as fp:
            data_dict = json.loads(fp.read())
        builder = ModelBuilder(args.class_name, data_dict[args.record_type])
        builder.create_base_module(args.class_file_path)
        builder.add_class(args.class_file_path)
    elif args.command == "generate_model":
        builder = DataDictionaryBuilder(args.isis_records_defs_csv_file_path)

        data_dictionary_json_file_path, ext = (
            os.path.splitext(args.isis_records_defs_csv_file_path)
        )
        data_dictionary_json_file_path += ".json"
        builder.save(data_dictionary_json_file_path)
        with open(data_dictionary_json_file_path) as fp:
            data_dict = json.loads(fp.read())

        builder = ModelBuilder(args.class_name, data_dict[args.record_type])

        class_dirname = os.path.dirname(args.class_file_path)
        class_basename = os.path.basename(args.class_file_path)

        base_class_basename = f"base_{class_basename}"
        base_class_file_path = os.path.join(
            class_dirname, base_class_basename)
        base_module_name, ext = os.path.splitext(base_class_basename)

        builder.create_base_module(base_class_file_path)
        builder.add_class(base_class_file_path)

        builder.create_main_module(args.class_file_path, base_module_name)
    else:
        parser.print_help()


if __name__ == '__main__':
    main()
