#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Jan 27 14:09:00 2018

@authors: Braden Carei (initial), Scott Hawley (revisions/parallelism)

Extracts metadata from Apple Loops .caf files
"""

import os
import subprocess
import errno
import shutil
from sys import version_info
import glob
from multiprocessing import Pool
from functools import partial
if version_info >= (3, 4):
    from plistlib import loads as readPlistFromString
else:
    from plistlib import readPlistFromString
import re


keys_to_use = ['kMDItemMusicalInstrumentName',
               'kMDItemMusicalInstrumentCategory',
               'kMDItemMusicalGenre',
               'kMDItemAppleLoopDescriptors',
               'kMDItemTimeSignature']


def make_dir(directory):  # makes a directory if it doesn't exist
    try:
        os.stat(directory)
    except:
        os.mkdir(directory)

def make_link(source, link_name):  # makes a symbolic link (unix) or shortcut (Windows):
    # TODO: how to do on windows?
    try:
        os.stat(link_name)
    except:
        os.symlink(source, link_name)


def pullTags_one_file(file_list, new_main_folder, file_index):
    # We actually pull the tags and create the directories & symbolic links as we go,
    #   and let the OS/filesystem handle 'collisions' if more than one process is trying to create the same thing
    infile = file_list[file_index]
    #print('infile = ',infile)
    output = subprocess.check_output(['mdls','-plist','-', infile])  # mdls is an Apple command-line utility
    plist = readPlistFromString(output)
    #print(plist)
    print_every = 100
    if (0 == file_index % print_every):
        print("pullTags:  File ",file_index,"/",len(file_list),": ",infile,sep="")
    for key in keys_to_use:
        #print(" Checking key = ",key)
        try:
            tags = plist[key]
            if tags:                       # guard against blank tags
                if isinstance(tags, str):
                    tags = [tags]      # just make it a length-1 list
                #print("               key = ",key,",   tags (list):")
                for tag in tags:
                    tag = re.sub('[^a-zA-Z\d\ ]|( ){2,}','_',tag )  # whitelist only certain characters. e.g. "4/4"->"4_4"
                    #tag = tag.replace("/", "-").replace(";", "-").replace("\\", "-").replace(" ", "_").replace
                    #print("                                                      [",tag,']',sep="")
                    if tag:              # guard against blank tags
                        new_folder = new_main_folder+'/'+tag
                        make_dir(new_folder)
                        link_name = new_folder+'/'+os.path.basename(infile)
                        make_link(infile, link_name)
        except:
            #print("Key error: File",infile,"doesn't contain key",key)
            pass
    return


def hawleys_way(args):
    search_dirs = args.dir
    new_main_folder='Samples/'
    make_dir(new_main_folder)

    print("Searching for .caf files in starting directories ",search_dirs)
    # Rescursively search for .caf files starting in various starting directories
    file_list = []
    for search_dir in search_dirs:
        for filename in glob.iglob(search_dir+'/**/*.caf', recursive=True):
            file_list.append(filename)

    # farm out the pulling of tags to many files simultaneously across all processors
    file_indices = tuple( range(len(file_list)) )
    cpu_count = os.cpu_count()
    if (False):                              # simple single-processor execution for testing
        for file_index in file_indices:
            pullTags_one_file(file_list,new_main_folder,file_index)
    else:
        pool = Pool(cpu_count)
        print("Mapping to",cpu_count,"processes")
        pool.map(partial(pullTags_one_file, file_list, new_main_folder), file_indices)
    return



def bradens_way():
    # Current working directory
    CWD = os.getcwd()

    UNKNOWN_FOLDER = "New Folder"

    # Folders
    folders = {
        UNKNOWN_FOLDER: []
    }

    # get a list of all the names of new folders
    for relative_path in os.listdir(CWD):
        full_path = os.path.join(CWD, relative_path)

        # If running from a script, skip self.
        #if os.path.samefile(full_path, __file__):
        #  continue

        try:
            output = subprocess.check_output(['mdls','-plist','-', full_path])

            plist = readPlistFromString(output)

            if (('kMDItemMusicalInstrumentName' in plist) or ('kMDItemMusicalInstrumentCategory' in plist)
                or ('kMDItemMusicalGenre' in plist) or ('kMDItemAppleLoopDescriptors' in plist)):

                tag1 = plist['kMDItemMusicalInstrumentName']
                tag2 = plist['kMDItemMusicalInstrumentCategory']
                tag3 = plist['kMDItemMusicalGenre']
                tag4 = plist['kMDItemAppleLoopDescriptors']

                if tag1 not in folders:
                        folders[tag1] = []

                if tag2 not in folders:
                        folders[tag2] = []

                if tag3 not in folders:
                        folders[tag3] = []

                if tag4 not in folders:
                        folders[tag4] = []

                new_path = os.path.join(CWD, tag1, relative_path)
                folders[tag1].append([full_path, new_path])

                new_path = os.path.join(CWD, tag2, relative_path)
                folders[tag2].append([full_path, new_path])

                new_path = os.path.join(CWD, tag3, relative_path)
                folders[tag3].append([full_path, new_path])

                new_path = os.path.join(CWD, tag4, relative_path)
                folders[tag4].append([full_path, new_path])

            else:
                # Move file to the catch-all folder
                new_path = os.path.join(CWD, UNKNOWN_FOLDER, relative_path)
                folders[UNKNOWN_FOLDER].append([full_path, new_path])

        except:
            print("Could not process: %s" % full_path)

    #Create folders and move files
    for (folder, tuples) in folders.items():
        folder_path = os.path.join(CWD, folder)
                #print(folder_path)
                # Create folder if it does not exist
        try:
            os.makedirs(folder_path)
            print("Created folder: %s" % folder_path)
        except OSError as exception:
            if exception.errno != errno.EEXIST:
                raise

        # Move files
        for t in tuples:
            try:
                shutil.copy(t[0],t[1])
                shutil.copy(t[0],t[2])
                shutil.copy(t[0],t[3])
                shutil.copy(t[0],t[4])
            except:
                print("Could not move file: %s" % t[0])
    return



if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description='Pull metadata tags from Apple Loops .caf files')
    parser.add_argument('dir', help="directory/ies to search in", nargs='*', default=['/Library/Audio'])
    args = parser.parse_args()
    hawleys_way(args)