#!/usr/bin/env python3


from subprocess import Popen, PIPE
from datetime import datetime
import json
from os.path import isfile, isdir
from os import walk

TYPES = [
        "JPEG",
        "JPG",
        "EXV",
        "CR2",
        "CRW",
        "MRW",
        "TIFF",
        "TIF",
        "WEBP",
        "DNG",
        "NEF",
        "FEF",
        "ARW",
        "RW2",
        "SR2",
        "SRW",
        "ORF",
        "PNG",
        "PGF",
        "RAF",
        "PSD",
        "JP2"
        ]

def extractor(input_files, start, end, recursive, extensions):
    if extensions:
        extensions = [ext.upper() for ext in extensions]
    if not isinstance(input_files, type(list())):
        raise ValueError("Input files must be a list.")
    exif_dict_list = []
    files = []
    usefull_exif = [
        "Image.Model",
        "Photo.LensModel",
        "Photo.FocalLength",
        "Photo.ApertureValue",
        "Photo.ExposureTime",
        "Photo.ISOSpeedRatings",
        "Photo.PixelXDimension",
        "Photo.PixelYDimension",
        "Image.ImageWidth",
        "Image.ImageLength",
        "Photo.DateTimeOriginal",
        "Photo.DateTimeDigitized",
        "Exif.Image.DateTime"
    ]
    for item in input_files:
        if isdir(item):
            for (dirpath, dirnames, filenames) in walk(item):
                for filename in filenames:
                    if filename.split(".")[-1].upper() in TYPES:
                        files.append("{}/{}".format(dirpath, filename))
                if not recursive:
                    break
        else:
            files.append(item)
    if extensions:
        files_with_ext_filter = []
        for filename in files:
            if filename.split(".")[-1].upper() in extensions:
                files_with_ext_filter.append("{}".format(filename))
        input_files = files_with_ext_filter
    else:
        input_files = files

    for input_file in input_files:
        exif_dict = {}
        if not isfile(input_file):
            raise ValueError("{} doesn't exist here.".format(input_file))
        exif_dict.update({'file': input_file})
        cmd = ["exiv2", "-Ptk", input_file]
        with Popen(cmd, stdout=PIPE) as extracted_data:
            extracted_data = extracted_data.stdout.read().decode()
            for line in extracted_data.splitlines():
                for exif in usefull_exif:
                    if exif in line.split()[0]:
                        exif_dict.update(
                                {
                                    line.split()[0].split(".")[-1]:
                                    " ".join(line.split()[1:])
                                    }
                                )
        for exif in usefull_exif:
            if exif.split(".")[-1] not in exif_dict:
                exif_dict.update({exif.split(".")[-1]: None})
        if exif_dict['FocalLength']:
            exif_dict['FocalLength'] = float(
                exif_dict['FocalLength'].replace(" mm", ""))
        if exif_dict['ApertureValue']:
            exif_dict['ApertureValue'] = float(
                exif_dict['ApertureValue'].replace("F", ""))
        if exif_dict['PixelXDimension'] and exif_dict['PixelYDimension']:
            exif_dict['PixelXDimension'] = int(exif_dict['PixelXDimension'])
            exif_dict['PixelYDimension'] = int(exif_dict['PixelYDimension'])
            pixels = exif_dict['PixelXDimension'] * \
                exif_dict['PixelYDimension']
        elif exif_dict['ImageLength'] and exif_dict['ImageWidth']:
            exif_dict['PixelXDimension'] = int(exif_dict['ImageLength'])
            exif_dict['PixelYDimension'] = int(exif_dict['ImageWidth'])
            pixels = exif_dict['PixelXDimension'] * \
                exif_dict['PixelYDimension']
        else:
            pixels = None
        if exif_dict['DateTimeOriginal']:
            exif_dict['DateTime'] = datetime.strptime(
                exif_dict['DateTimeOriginal'], "%Y:%m:%d %H:%M:%S")
        elif exif_dict['DateTimeDigitized']:
            exif_dict['DateTime'] = datetime.strptime(
                    exif_dict['DateTimeDigitized'], "%Y:%m:%d %H:%M:%S")
        elif exif_dict['DateTime']:
            exif_dict['DateTime'] = datetime.strptime(
                    exif_dict['DateTime'], "%Y:%m:%d %H:%M:%S")
        else:
            exif_dict['DateTime'] = None
        if exif_dict['ExposureTime']:
            try:
                exif_dict['ExposureTime'] = float(
                    exif_dict['ExposureTime'].split(" ")[0])
            except ValueError:
                exposure = exif_dict['ExposureTime'].split(" ")[0]
                exif_dict['ExposureTime'] = float(
                    1/(float(exposure.split("/")[1])))
        if pixels:
            exif_dict.update({'Dimension': round((pixels/10**6), 1)})
        else:
            exif_dict.update({'Dimension': None})
        exif_dict_list.append(exif_dict)
    cameras, lenses, focals, apertures, exposures = {}, {}, {}, {}, {}
    isos, dimensions, cameras_lenses, dates = {}, {}, {}, {}
    cameras_list, lenses_list, focals_list, apertures_list, exposures_list = [], [], [], [], []
    isos_list, dimensions_list, cameras_lenses_list, dates_list = [], [], [], []
    for data in exif_dict_list:
        if data['DateTime'] and start <= data['DateTime'] <= end:
            cameras_list.append(data['Model'])
            lenses_list.append(data['LensModel'])
            focals_list.append(data['FocalLength'])
            apertures_list.append(data['ApertureValue'])
            exposures_list.append(data['ExposureTime'])
            isos_list.append(data['ISOSpeedRatings'])
            dimensions_list.append(data['Dimension'])
            if data['LensModel']:
                cameras_lenses_list.append(
                    "{} + {}".format(data['Model'], data['LensModel']))
            dates_list.append(data['DateTime'].strftime("%Y%m%d"))
    for camera in list(set(cameras_list)):
        if camera:
            cameras.update({camera: cameras_list.count(camera)})
    for lens in list(set(lenses_list)):
        if lens:
            lenses.update({lens: lenses_list.count(lens)})
    for camera_lens in list(set(cameras_lenses_list)):
        if camera_lens:
            cameras_lenses.update(
                {camera_lens: cameras_lenses_list.count(camera_lens)})
    for focal in list(set(focals_list)):
        if focal:
            focals.update({focal: focals_list.count(focal)})
    for aperture in list(set(apertures_list)):
        if aperture:
            apertures.update({aperture: apertures_list.count(aperture)})
    for exposure in list(set(exposures_list)):
        if exposure:
            exposures.update(
                {float(exposure*1000): exposures_list.count(exposure)})
    for iso in list(set(isos_list)):
        if iso:
            isos.update({iso: isos_list.count(iso)})
    for dimension in list(set(dimensions_list)):
        if dimension:
            dimensions.update({dimension: dimensions_list.count(dimension)})
    for date in list(set(dates_list)):
        if date:
            dates.update({date: dates_list.count(date)})
    return {
        "total": len(dimensions_list),
        "date": dates,
        "cameras": cameras,
        "lenses": lenses,
        "cameras+lenses": cameras_lenses,
        "focals": focals,
        "apertures": apertures,
        "shutter_speeds": exposures,
        "isos": isos,
        "dimensions": dimensions
    }


if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "infile", help="input file (EXV, JPG, CR2, ...). Can be more than one.", nargs='+')
    parser.add_argument("-s", "--start-date", help="start date as YYYYMMDD. If omitted, begin of the collection.", const=None)
    parser.add_argument("-e", "--end-date", help="end date as YYYYMMDD. If omitted, today", const=None)
    parser.add_argument("-R", "--recursive", help="Walk throught directory to find all files", action='store_true')
    parser.add_argument("-x", "--extension", help="Read files with this extension only (can be specified more than one time)", action='append')
    args = parser.parse_args()
    if not args.recursive:
        recursive = False
    if not args.extension:
        extensions = None
    else:
        extensions = args.extension
    if not args.start_date:
        args.start_date = "18250101" # Ok, that's the year of the 1st photography by Nicéphore Niépce. Shoul'd be enough for a start date.
    if not args.end_date:
        args.end_date = datetime.strftime(datetime.now(), "%Y%m%d")
    try:
        datetime.strptime(args.start_date, "%Y%m%d")
        datetime.strptime(args.end_date, "%Y%m%d")
    except ValueError:
        print("Date must be YYYYMMDD.")
        exit()
    recursive = args.recursive
    print(json.dumps(extractor(args.infile, datetime.strptime(args.start_date, "%Y%m%d"), datetime.strptime(args.end_date, "%Y%m%d"), recursive, extensions), indent=2, sort_keys=True))
