#!/usr/bin/env python3
"""
NAME
    sorter - A tool for sorting and backing up photos

SYNOPSIS
    sorter [OPTION]... DIRECTORY..."

DESCRIPTION
    Copy (or move) all of the images from the DIRECTORY to a destination
    based on the date and time read from the EXIF data within the images.

    Files will be saved to DEST/<year>/<month>/<day>/<filename>, DEST
    defaults to ~/Pictures,
    e.g. ~/Pictures/2015/January/23_Jan_15/IMG_0633.jpg

    If more than one directory is specified then images will be copied
    from each directory listed in turn.

    -h, --help
        Prints this help then exit
    -s, --skip
        If a file with the same name is already in the destination folder
        it will be skipped rather than prompting the user
    -m, --move
        Move instead of copy the files.
    -d, --dest=
        Defines DEST to be something other than ~/Pictures
    -v, --verbose
        Prints out each copy or directory creation
"""
import sys
import os
import time
import shutil
import getopt
import time

# apt install python3-exifread on Ubuntu 22.04 works
import exifread

"""
Copyright (c) 2023, Nathan Dumont
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the author nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""

def error_exit(message, code):
    print(message)
    print("Try 'sorter --help' for more information.")
    sys.exit(code)

try:
    opts, args = getopt.getopt(sys.argv[1:], "hsd:vm", ["help", "skip", "dest=", "verbose", "move"])
except getopt.GetoptError as err:
    error_exit(str(err))

dest = os.path.expanduser("~/Pictures")
skip = False
verbose = False
move = False
for o, a in opts:
    if o in ("-h", "--help"):
        print(__doc__)
        sys.exit(0)
    elif o in ("-m", "--move"):
        move = True
    elif o in ("-s", "--skip"):
        skip = True
    elif o in ("-d", "--dest"):
        if not os.path.isdir(a):
            error_exit("DEST must be an existing directory", -2)
        dest = a
    elif o in ("-v", "--verbose"):
        verbose = True

if len(args) < 1:
    error_exit("No input folders provided", -1)

for d in args:
    if not os.path.isdir(os.path.expanduser(d)):
        error_exit(d + " is not a directory", -1)

for d in args:
    # list the contents of the folder
    files = os.listdir(os.path.expanduser(d))

    # work through all the files in the folder
    for f in files:
        # only process JPEG images with a .jpg extension
        # note the lower because cameras usually use FAT which is non-case sensitive
        # so the case switches arbitrarily depending on drivers
        if f[-4:].lower() == ".jpg":
            with open(os.path.join(os.path.expanduser(d), f), "rb") as fr:
                tags = exifread.process_file(fr, stop_tag='DateTimeOriginal')
                ts = time.strptime(str(tags['EXIF DateTimeOriginal']), "%Y:%m:%d %H:%M:%S")
            # build the new directory path
            target_dir = os.path.join(dest,
                            time.strftime("%Y", ts),
                            time.strftime("%B", ts),
                            time.strftime("%d_%b_%y", ts))
            # add the file name to the source and dest directories
            target = os.path.join(target_dir, f)
            source = os.path.join(d, f)
            # make the directory if it doesn't exist, and any parents
            # using makedirs, an error occurs if you try and make an existing directory
            # so we need to check it isn't already there first
            if not os.path.isdir(target_dir):
                if verbose:
                    print(f"makedirs({target_dir})")
                os.makedirs(target_dir)     # works like "mkdir -p" on POSIX systems
            # to make this a little more "safe", we now check that we aren't overwriting
            # anything.  If we are the user gets to choose
            # default to doing the write, the user changes this if the file exists
            a = "o"
            if os.path.isfile(target):
                if skip:
                    a = "s"
                else:
                    # ask the user what to do
                    a = input("File exists (O)verwrite, (S)kip - default or (A)bort script? ")
            # use lower() to ignore the case
            # user said overwrite so just do the copy anyway
            if a.lower() == "o":
                if move:
                    if verbose:
                        print(f"move({source},{target})")
                    shutil.move(source, target)
                else:
                    if verbose:
                        print(f"copy({source},{target})")
                    shutil.copy(source, target)
            # the user said abort the script so exit python
            elif a.lower() == "a":
                sys.exit(-2)
            # any other input does the default which is to skip the file.  Note if
            # you are using move this will leave the file in the source location
