# Copyright (C) 2020-2022 HERE Global B.V. and its affiliate(s).
# All rights reserved.
#
# This software and other materials contain proprietary information
# controlled by HERE and are protected by applicable copyright legislation.
# Any use and utilization of this software and other materials and
# disclosure to any third parties is conditional upon having a separate
# agreement with HERE for the access, use, utilization or disclosure of this
# software. In the absence of such agreement, the use of the software is not
# allowed.

"""
This module contains helper functions for various file operations.
"""

import hashlib
import zlib
from pathlib import Path
from typing import Optional, Union


def checksum(
    path_or_data: Union[str, Path, bytes], hash_algo: str, chunk_num_blocks: int = 128
) -> str:
    """
    Calculate checksum for data in a file based on input hashing algorithm.

    :param path_or_data: A string representing path of the file.
    :param hash_algo: A string representing name of hashing algorithm.
    :param chunk_num_blocks: An int representing block size.
    :return: A checksum string.
    :raises ValueError: If file does not exist or `hash_algo` is not valid.
    """
    hash_algo = (hash_algo.replace("-", "")).lower()
    if hash_algo not in hashlib.algorithms_guaranteed:
        raise ValueError(f"Algorithm {hash_algo} is not supported by hashlib.")
    hash_factory = getattr(hashlib, hash_algo)
    hash_obj = hash_factory()
    if isinstance(path_or_data, bytes):
        hash_obj.update(path_or_data)
    else:
        if not Path(path_or_data).is_file():
            raise ValueError(f"File: {path_or_data} does not exist.")
        with open(path_or_data, "rb") as f:
            for chunk in iter(lambda: f.read(chunk_num_blocks * hash_obj.block_size), b""):
                hash_obj.update(chunk)
    return str(hash_obj.hexdigest())


def get_crc(path_or_data: Union[str, Path, bytes]):
    """
    Calculate crc for data.
    """
    crc: Optional[str] = None
    if isinstance(path_or_data, bytes):
        crc = str(zlib.crc32(path_or_data))
        crc = "%X" % (int(crc) & 0xFFFFFFFF)
    else:
        buffersize = 262144  # 256 KB
        with open(path_or_data, "rb") as afile:
            buffr = afile.read(buffersize)
            crcvalue = 0
            while len(buffr) > 0:
                crcvalue = zlib.crc32(buffr, crcvalue)
                buffr = afile.read(buffersize)
        crc = "%X" % (crcvalue & 0xFFFFFFFF)
    return crc


def get_readable_bytes(size: int) -> str:
    """
    Get a human-readable representation of the given number of bytes.

    :param size: the number of bytes
    :return: the size scaled to KB, MB, GB or TB with units
    """
    power_labels = {40: "TB", 30: "GB", 20: "MB", 10: "KB"}
    for power, label in power_labels.items():
        if size >= 2**power:
            approx_size = size // 2**power
            return f"{approx_size} {label}"
    return f"{size} bytes"
