#!/usr/bin/python -B

import os
import sys

from config import *
import utils
import comparator
import normalize


def check_args():
  """Checks the command line arguments.

  Prints a message to standard error if the arguments are incorrect.

  Returns:
    True if the arguments are correct; otherwise False.
  """
  usage = ['ERROR  {0} -f <path> -f <path>\n',
           'ERROR  {0} -f <path> -d <path>\n',
           'ERROR  {0} -d <path> -f <path>\n',
           'ERROR  {0} -d <path> -d <path>\n']
  args_good = (len(sys.argv) == 5 and
               ((sys.argv[1] == '-f' and os.path.isfile(sys.argv[2])) or
                (sys.argv[1] == '-d' and os.path.isdir(sys.argv[2]))) and
               ((sys.argv[3] == '-f' and os.path.isfile(sys.argv[4])) or
                (sys.argv[3] == '-d' and os.path.isdir(sys.argv[4]))))
  if not args_good:
    sys.stderr.write('ERROR Usage: one of\n')
    sys.stderr.writelines([s.format(sys.argv[0]) for s in usage])
    return False
  return True


def check_file(path):
  """Checks if a given file is in either MP3 or WAVE format.

  Prints a message to standard error if the file is not in either format.

  Args:
    path: The path to the file to be checked.

  Returns:
    True if the file is in either MP3 or WAVE format; otherwise False.
  """
  if not (utils.is_mp3(path) or utils.is_wave(path)):
    sys.stderr.write('ERROR {0}: invalid format for {1}: input files must be '
                     'of MP3 or WAVE format\n'.format(sys.argv[0], path))
    return False
  return True


def get_paths(is_file, path):
  """Returns a list of file paths described by a path.

  Args:
    is_file: Whether the path names a regular file or a directory.
    path: The path to investigate.

  Returns:
    If is_file is true, a list containing path; otherwise, a list
    containing the contents of the directory named by path.
  """
  if is_file:
    return [path]
  path = os.path.normpath(path)
  return [os.path.join(path, f) for f in os.listdir(path)]


def get_data(path):
  """Normalizes the file led to by the given path, then calculates and returns
  the MFCCs of the normalized representation.

  Args:
    path: The path leading to an MP3 or WAVE file.

  Returns:
    The array of the MFCCs of the normalized representation of the file
    led to by path.
  """
  f = normalize.normalize_file(path)
  mfccs = normalize.get_mfcc(f.name)
  f.close()
  return mfccs


def print_matches(paths1, paths2):
  """Finds and prints matches between two sets of files.

  For every pair of files from the two sets, prints 'MATCH ' followed by
  the base names of the two files if they match, or prints 'NO MATCH' if
  they don't. 'MATCH' lines might also have parenthesized debugging
  information following the two base names.

  Args:
    paths1: An iterable of paths.
    paths2: Another iterable of paths.
  """
  data1 = [get_data(path) for path in paths1]
  data2 = [get_data(path) for path in paths2]
  cache = set() # (shorter, longer)
  for i in range(len(data1)):
    path1 = paths1[i]
    for j in range(len(data2)):
      path2 = paths2[j]
      if (path1, path2) in cache or (path2, path1) in cache or path1 == path2:
        match = True
      else:
        mfccs1 = data1[i]
        mfccs2 = data2[j]
        match = comparator.compare(mfccs1, mfccs2, MFCC_MATCH_THRESHOLD)
        if match:
          if match == -1:
            cache.add((path1, path2))
          elif match == 1:
            cache.add((path2, path1))
          while True:
            addition = set((a, y)
                           for a, b in cache for x, y in cache if b == x)
            if addition <= cache:
              break
            cache = cache | addition
      if match:
        print 'MATCH {0} {1} ({2})'.format(os.path.basename(path1),
                                           os.path.basename(path2),
                                           match)
      else:
        print 'NO MATCH'


def main():
  # Check if syntax is correct
  if not check_args():
    sys.exit(1)
  # Verify format of input files
  paths1 = get_paths(sys.argv[1] == '-f', sys.argv[2])
  paths2 = get_paths(sys.argv[3] == '-f', sys.argv[4])
  # Filter out input of improper format
  good_paths1 = [f for f in paths1 if check_file(f)]
  good_paths2 = [f for f in paths2 if check_file(f)]
  # Stop here if at least one group of files is empty
  if not good_paths1 or not good_paths2:
    sys.exit(1)
  # Check for matches between all possible pairs of files
  print_matches(good_paths1, good_paths2)
  # Exit with error code 1 if any input files were in an improper format
  if len(paths1) != len(good_paths1) or len(paths2) != len(good_paths2):
    sys.exit(1)


if __name__ == '__main__':
  main()
