import json
from zipfile import ZipFile, is_zipfile
from pathlib import Path
from typing import List, Union
import os
import re
from io import FileIO


def find_new_icons_in_devicon_json(devicon_json_path: str, icomoon_json_path: str):
    """
    Find the newly added icons by finding the difference between
    the devicon.json and the icomoon.json.
    :param devicon_json_path, the path to the devicon.json.
    :param icomoon_json_path: a path to the iconmoon.json.
    :return: a list of the new icons as JSON objects.
    """
    devicon_json = get_json_file_content(devicon_json_path)
    icomoon_json = get_json_file_content(icomoon_json_path)

    new_icons = []
    for icon in devicon_json:
        if is_not_in_icomoon_json(icon, icomoon_json):
            new_icons.append(icon)

    return new_icons


def get_json_file_content(json_path: str):
    """
    Get the json content of the json_path.
    :param: json_path, the path to the json file.
    :return: a dict representing the file content.
    """
    with open(json_path) as json_file:
        return json.load(json_file)


def is_not_in_icomoon_json(icon, icomoon_json):
    """
    Checks whether the icon's name is not in the icomoon_json.
    :param icon: the icon object we are searching for.
    :param icomoon_json: the icomoon json object parsed from
    icomoon.json.
    :return: True if icon's name is not in the icomoon.json, else False.
    """
    pattern = re.compile(f"^{icon['name']}-")

    for font in icomoon_json["icons"]:
        if pattern.search(font["properties"]["name"]):
            return False
    return True


def get_svgs_paths(new_icons: List[dict], icons_folder_path: str, 
    icon_versions_only: bool=False, as_str: bool=True):
    """
    Get all the suitable svgs file path listed in the devicon.json.
    :param new_icons, a list containing the info on the new icons.
    :param icons_folder_path, the path where the function can find the
    listed folders.
    :param icon_versions_only, whether to only get the svgs that can be 
    made into an icon. 
    :param: as_str, whether to add the path as a string or as a Path.
    :return: a list of svg file paths that can be uploaded to Icomoon.
    """
    file_paths = []
    for icon_info in new_icons:
        folder_path = Path(icons_folder_path, icon_info['name'])

        if not folder_path.is_dir():
            raise ValueError(f"Invalid path. This is not a directory: '{folder_path}'.")

        if icon_versions_only:
            get_icon_svgs_paths(folder_path, icon_info, file_paths, as_str)
        else:
            get_all_svgs_paths(folder_path, icon_info, file_paths, as_str)
    return file_paths


def get_icon_svgs_paths(folder_path: Path, icon_info: dict,
    file_paths: List[Union[str, Path]], as_str: bool):
    """
    Get only the svg file paths that can be made into an icon from the icon_info.
    :param: folder_path, the folder where we can find the icons.
    :param: icon_info, an icon object in the devicon.json.
    :param: file_paths, an array containing all the file paths found.
    :param: as_str, whether to add the path as a string or as a Path.
    """
    aliases = icon_info["aliases"]

    for font_version in icon_info["versions"]["font"]:
        # if it's an alias, we don't want to make it into an icon
        if is_alias(font_version, aliases):
            print(f"Finding SVG filepaths: skipping this font since it's an alias: {icon_info['name']}-{font_version}.svg")
            continue

        file_name = f"{icon_info['name']}-{font_version}.svg"
        path = Path(folder_path, file_name)

        if path.exists():
            file_paths.append(str(path) if as_str else path)
        else:
            raise ValueError(f"This path doesn't exist: '{path}'")


def get_all_svgs_paths(folder_path: Path, icon_info: dict,
    file_paths: List[Union[str, Path]], as_str: bool):
    """
    Get all the svg file paths of an icon.
    :param: folder_path, the folder where we can find the icons.
    :param: icon_info, an icon object in the devicon.json.
    :param: file_paths, an array containing all the file paths found.
    :param: as_str, whether to add the path as a string or as a Path.
    """
    for font_version in icon_info["versions"]["svg"]:
        file_name = f"{icon_info['name']}-{font_version}.svg"
        path = Path(folder_path, file_name)

        if path.exists():
            file_paths.append(str(path) if as_str else path)
        else:
            raise ValueError(f"This path doesn't exist: '{path}'")


def is_alias(font_version: str, aliases: List[dict]):
    """
    Check whether the font version is an alias of another version.
    :return: True if it is, else False.
    """
    for alias in aliases:
        if font_version == alias["alias"]:
            return True
    return False


def extract_files(zip_path: str, extract_path: str, logfile: FileIO, delete=True):
    """
    Extract the style.css and font files from the devicon.zip
    folder. Must call the gulp task "get-icomoon-files"
    before calling this.
    :param zip_path, path where the zip file returned
    from the icomoon.io is located.
    :param extract_path, the location where the function
    will put the extracted files.
    :param delete, whether the function should delete the zip file
    when it's done.
    :param logfile
    """
    print("Extracting zipped files...", file=logfile)
    fixBadZipfile(zip_path, logfile)
    print(f"it's zipped {is_zipfile(zip_path)}", file=logfile)
    icomoon_zip = ZipFile(zip_path)
    target_files = ('selection.json', 'fonts/', 'fonts/devicon.ttf',
                    'fonts/devicon.woff', 'fonts/devicon.eot',
                    'fonts/devicon.svg', "style.css")
    for file in target_files:
        icomoon_zip.extract(file, extract_path)

    print("Files extracted", file=logfile)

    if delete:
        print("Deleting devicon zip file...", file=logfile)
        icomoon_zip.close()
        os.remove(zip_path)


def fixBadZipfile(zippath: str, logfile: FileIO):  
    """
    Fix a bad zipfile (one that causes zipfile.ZipFile to throw a BadZipfile Error).
    Taken from https://stackoverflow.com/a/11385480/11683637.
    """
    f = open(zippath, 'r+b')  
    data = f.read()  
    pos = data.find(b'\x50\x4b\x05\x06') # End of central directory signature  
    if (pos > 0):  
        # self._log("Trancating file at location " + str(pos + 22)+ ".")  
        f.seek(pos + 22)   # size of 'ZIP end of central directory record' 
        f.truncate()  
    else:
        print("Zipfile don't need to be fixed", file=logfile)

    f.close() 


def rename_extracted_files(extract_path: str, logfile: FileIO):
    """
    Rename the extracted files selection.json and style.css.
    :param extract_path, the location where the function
    can find the extracted files.
    :return: None.
    """
    print("Renaming files", file=logfile)
    old_to_new_list = [
        {
            "old": Path(extract_path, "selection.json"),
            "new": Path(extract_path, "icomoon.json")
        },
        {
            "old": Path(extract_path, "style.css"),
            "new": Path(extract_path, "devicon-base.css")
        }
    ]

    for dict_ in old_to_new_list:
        os.replace(dict_["old"], dict_["new"])

    print("Files renamed", file=logfile)


def create_screenshot_folder(dir, screenshot_name: str="screenshots/"):
    """
    Create a screenshots folder in the dir.
    :param dir, the dir where we want to create the folder.
    :param screenshot_name, the name of the screenshot folder.
    :raise Exception if the dir provided is not a directory.
    :return the string name of the screenshot folder.
    """
    folder = Path(dir).resolve()
    if not folder.is_dir():
        raise Exception(f"This is not a dir: {str(folder)}. \ndir must be a valid directory")

    screenshot_folder = Path(folder, screenshot_name)
    try:
        os.mkdir(screenshot_folder)
    except FileExistsError:
        print(f"{screenshot_folder} already exist. Not creating new folder.")
    finally:
        return str(screenshot_folder)

def write_to_file(path: str, value):
    """
    Write the value to a file.
    """
    with open(path, "w") as file:
        file.write(value)

# --- NOT USED CURRENTLY --- 
def get_added_modified_svgs(files_added_json_path: str,
    files_modified_json_path: str):
    """
    Get the svgs added and modified from the files_changed_json_path.
    :param: files_added_json_path, the path to the files_added.json created by the gh-action-get-changed-files@2.1.4
    :param: files_modified_json_path, the path to the files_modified.json created by the gh-action-get-changed-files@2.1.4
    :return: a list of the svg file paths that were added/modified in this pr as Path. It will only return icons in /icons path (see https://github.com/devicons/devicon/issues/505)
    """
    files_added = get_json_file_content(files_added_json_path)
    files_modified = get_json_file_content(files_modified_json_path)

    svgs = []
    for file in files_added:
        path = Path(file)
        if path.suffix.lower() == ".svg" and path.as_posix().lower().startswith('icons/'):
            svgs.append(path)

    for file in files_modified:
        path = Path(file)
        if path.suffix.lower() == ".svg" and path.as_posix().lower().startswith('icons/'):
            svgs.append(path)
    
    return svgs
