#!/usr/bin/env nix-shell
#! nix-shell -i python3 --pure
#! nix-shell -p python3 python3Packages.coloredlogs python3Packages.exifread

import argparse
import datetime
import logging
import os
import re
import typing

import coloredlogs
import exifread

log = logging.getLogger(__name__)
coloredlogs.install(level="DEBUG", fmt="%(levelname)s %(message)s", logger=log)

EXTENSION_PATTERN = re.compile(r"\.(JPE?G|DNG)", re.I)
COMMON_PATTERN = re.compile(r"(IMG|DSC[NF]?|100|P10|f|t)_?\d+", re.I)
EXIF_TAG_ID = 0x9003  # DateTimeOriginal
EXIF_DATE_FORMAT = "%Y:%m:%d %H:%M:%S"


def get_pictures(directory: str = ".", skip_renamed: bool = True) -> typing.Generator:
    for root, _, files in os.walk(directory):
        for filename in files:
            filename_trunk, extension = os.path.splitext(filename)

            if not re.match(EXTENSION_PATTERN, extension):
                continue
            if skip_renamed:
                if not re.match(COMMON_PATTERN, filename_trunk):
                    continue
            full_path = os.path.join(root, filename)
            yield full_path


def main(args: argparse.Namespace) -> None:
    log.warning("Counting files...")
    kwargs = {"directory": args.dir, "skip_renamed": args.skip_renamed}
    log.warning("Processing files...")
    for full_path in get_pictures(**kwargs):
        # Find date
        with open(full_path, "rb") as fd:
            exif_data = exifread.process_file(fd)
        if not exif_data:
            log.warning(f"{full_path} does not have EXIF data")
        for ifd_tag in exif_data.values():
            if ifd_tag.tag == EXIF_TAG_ID:
                date_raw = ifd_tag.values
                break
        else:
            log.warning(f"{full_path} does not have required EXIF tag")
            continue
        date = datetime.datetime.strptime(date_raw, EXIF_DATE_FORMAT)

        # Determine new filename
        ext = os.path.splitext(full_path)[1].lower()
        if ext == ".jpeg":
            ext = ".jpg"
        new_name = date.isoformat().replace(":", "-").replace("T", "_") + args.suffix
        # First substitution is to allow images being sent to a NTFS filesystem
        # Second substitution is for esthetics
        new_path = os.path.join(args.dir, f"{new_name}{ext}")
        # TODO Allow keeping image in same folder
        i = 0
        while os.path.exists(new_path):
            if full_path == new_path:
                break
            log.debug(f"{new_path} already exists, incrementing")
            i += 1
            new_path = os.path.join(args.dir, f"{new_name}_{i}{ext}")

        # Rename file
        if full_path == new_path:
            log.debug(f"{full_path} already at required filename")
            continue
        log.info(f"{full_path} →\t{new_path}")
        if os.path.exists(new_path):
            raise FileExistsError(f"Won't overwrite {new_path}")
        if not args.dry:
            os.rename(full_path, new_path)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Rename images based on their dates")
    parser.add_argument(
        "dir",
        metavar="DIRECTORY",
        type=str,
        default=".",
        nargs="?",
        help="Directory containing the pictures",
    )
    parser.add_argument(
        "-d",
        "--dry",
        action="store_true",
        help="Do not actually rename, just show old and new path",
    )
    parser.add_argument(
        "-r",
        "--skip-renamed",
        action="store_true",
        help="Skip images whose filename doesn't match usual camera output filenames.",
    )
    parser.add_argument(
        "-s",
        "--suffix",
        default="",
        help="Text to add before the extension",
    )
    args = parser.parse_args()
    main(args)