import unreal
import re
import os
import hashlib

log_category = "GameplayTagsToCpp"

def get_file_hash(file_to_hash):
    # Hashing: Hash into bytes instead of hexidecimal. These are not for the eyes of humanity.
    BLOCK_SIZE = 16384 # 16 KB block size (smaller than usual to run on a potato)
    hash_OUT = hashlib.sha256()
    with open(file_to_hash, "rb") as tf:
        chunk = tf.read(BLOCK_SIZE) # Read first block
        while len(chunk) > 0:
            hash_OUT.update(chunk)
            chunk = tf.read(BLOCK_SIZE) # Read next block, then loop if necessary
    return hash_OUT.digest()

# Note: Output is <bool bAreHashesIdentical, byte[] newHash>
def compare_file_hashes(hash_path_IN):
    print("{}: Checking path: {}".format(log_category, hash_path_IN))
    if (os.path.exists(hash_path_IN)):
        print("{}: Found hash file, parsing...".format(log_category))
        with open(hash_path_IN, "rb") as hash_file:
            existing_hash_bytes = hash_file.read()
    else:
        print("{}: No hash file exists, need update".format(log_category))
        existing_hash_bytes = bytearray() # Initialize with nothing, because there is no existing hash
        
    # Hashing: Hash into bytes instead of hexidecimal. These are not for the eyes of humanity.
    defaulttags_path = "{}DefaultGameplayTags.ini".format(unreal.Paths.project_config_dir())
    new_hash_bytes = get_file_hash(defaulttags_path)

    print("{}: Compare: {} ?= {}".format(log_category, existing_hash_bytes, new_hash_bytes))
    return (existing_hash_bytes == new_hash_bytes),new_hash_bytes

def write_hash_to_file(hash_bytes_IN, hash_path_IN):
    if not(os.path.exists(hash_path_IN)):
        print("{}: Creating file: {}".format(log_category, hash_path_IN))
        open(hash_path_IN, "a").close()
    
    # This file should never be locked, as it should only ever be auto-generated and never committed.
    # but if you do get a  locking issue, uncomment this:
    #os.chmod(hash_path_IN, 0o755)

    print("{}: Writing hash {} to file {}".format(log_category, hash_bytes_IN, hash_path_IN))

    with open(hash_path_IN, "wb") as hf:
        hf.write(hash_bytes_IN)


print("Checking if gameplay tags have been modified")

hash_file_path = "{}{}".format(unreal.Paths.project_saved_dir(), "TagHash")

# Tuple<bool bAreHashesIdentical, byte[] newHash>
hash_check = compare_file_hashes(hash_file_path)
# if (hash_check[0]):
#     print("{}: Hashes are identical, no need to regenerate gameplay tags".format(log_category))
#     exit()

# update hashfile and continue
write_hash_to_file(hash_check[1], hash_file_path)


print("Regenerating OLSGameplayTags.h")
file_location = "{}OLS/Public/OLSGameplayTags.h".format(unreal.Paths.game_source_dir())

gtag_set = set()

def fill_gtag_set(gtag):
    if(gtag not in gtag_set):
        gtag_set.add(gtag)
    split_tag_array = gtag.rsplit(".", 1)
    if(len(split_tag_array)>1):
        fill_gtag_set(split_tag_array[0])

# force file to be writeable in workspace (we'll try to check it out from SourceControl at the end of this script)
os.chmod(file_location, 0o755)
with open(file_location, "wb") as f:
    f.write(b"// (c) 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.\n\n")
    f.write(b"#pragma once\n\n")
    f.write(b"/* **************************************************/\n")
    f.write(b"/* DO NOT EDIT THIS FILE MANUALLY!                  */\n")
    f.write(b"/* **************************************************/\n")
    f.write(b"/* This file is autogenerated by a python script.   */\n")
    f.write(b"/* See: gameplaytags_to_cpp.py                      */\n")
    f.write(b"/* **************************************************/\n")
    f.write(b"/* DO NOT EDIT THIS FILE MANUALLY!                  */\n")
    f.write(b"/* **************************************************/\n")
    f.write(b"\n")

    gameplay_tags_container = unreal.OLSGameplayTagsBlueprintLibrary.request_all_gameplay_tags(True)
    for gtag in gameplay_tags_container.gameplay_tags:
        gtag_string = unreal.StringLibrary.conv_name_to_string(gtag.get_editor_property("tag_name"))
        fill_gtag_set(gtag_string)

    sorted_tags = sorted(gtag_set, key=lambda s: s.lower())
    for gtag_string in sorted_tags:
        tag_sanitized_name = re.sub("\.", "_", gtag_string)
        as_macro = "#define GTag_{} TEXT(\"{}\")".format(tag_sanitized_name, gtag_string)
        # print as_macro
        f.write((as_macro + "\n").encode('utf-8'))


# checking out can be slow and fail if SourceControl is not working
# try checking out after the file has been successfully written to disk
if unreal.SourceControl.is_enabled() and unreal.SourceControl.is_available():
    has_checked_out_file = unreal.SourceControl.check_out_or_add_file(file_location)
    if(not has_checked_out_file):
        unreal.log_warning("Is Perforce down? Failed to check out file: {}".format(file_location))