#!/usr/bin/env python

# use python3 version of `print' for compatibility
from __future__ import print_function

import os
from os import path
from stat import *
import sys
import re
from optparse import OptionParser
import fnmatch
import datetime
import subprocess
import time

DARUMA_CLIENT_COMMAND='darumaClient'

FILE_PATTERN='^log-([0-9]+)-from-client$'
FILE_NUMBER_INDEX=1


#
# analyze command line arguments
#
option_parser = OptionParser(usage = 'Usage: %prog [options] [DIR_OR_FILE ...]')
option_parser.remove_option( '-h' )
option_parser.add_option( '--help',
                          dest='help',
                          action='store_true',
                          default=False,
                          help='show this help message and exit' )
option_parser.add_option( '-h', '--host',
                          dest='host',
                          action='store',
                          type='string',
                          default='localhost',
                          help='specify DaRuMa server host' )
option_parser.add_option( '-p', '--port',
                          dest='port',
                          action='store',
                          type='int',
                          default='5050',
                          help='specify DaRuMa server port' )
option_parser.add_option( '-w', '--wait',
                          dest='do_wait',
                          action='store_true',
                          default=True,
                          help='wait different time of file timestamps (default)' )
option_parser.add_option( '-W', '--no-wait',
                          dest='do_wait',
                          action='store_false',
                          default=True,
                          help='do not wait' )
option_parser.add_option( '--fixed-wait',
                          dest='real_number_wait_seconds',
                          type='float',
                          action='store',
                          default=None,
                          help='wait fixed time' )
option_parser.add_option( '--print-io',
                          dest='print_io',
                          action='store_true',
                          default=False,
                          help='print input/output message' )
option_parser.add_option( '-x', '--exclude-glob',
                          dest='exclude_glob',
                          action='append',
                          type='string',
                          help='specify file pattern to ignore' )
option_parser.add_option( '-v', '--verbose',
                          dest='verbose',
                          action='store_true',
                          default=False,
                          help='print verbose messages to standard error' )
option_parser.add_option( '-V', '--no-verbose',
                          dest='verbose',
                          action='store_false',
                          default=False,
                          help='inhibit verbose messages (default)' )

(opts, specified_dir_or_files) = option_parser.parse_args()

if opts.help:
    option_parser.print_help()
    exit()

if len(specified_dir_or_files) == 0:
    specified_dir_or_files.append( '.' )

exclude_globs = opts.exclude_glob
host = opts.host
port = opts.port
verbose = opts.verbose
do_wait = opts.do_wait
print_io = opts.print_io
fixed_wait_seconds = opts.real_number_wait_seconds

def check_port( port ):
    return 0 < port and port <= 65535

def check_host( host ):
    return re.compile('^[-\w\.\%]+$').match( host ) != None

if not check_port( port ):
    sys.stderr.write( 'invalid port number "' + str(port) + '"!!\n' )
    exit( 1 )

if not check_host( host ):
    sys.stderr.write( 'invalid host "' + host + '"!!\n' )
    exit( 1 )

#
# set python setting
#
os.stat_float_times( True )


#
# check external command
#
if subprocess.Popen( ['which', DARUMA_CLIENT_COMMAND],
                     stdout=subprocess.PIPE ).communicate()[0] == '':
    sys.stderr.write( DARUMA_CLIENT_COMMAND + ' not found!!\n' )
    exit( 1 )


#
# utility functions
#
def execute_command( command, **args ):
    try:
        retcode = subprocess.call( command, **args )
        if retcode != 0:
            sys.stderr.write( 'execution failed, command = ' + str(command)
                              + ', return code = ' + str(retcode) + '\n' )
            exit( 1 )

    except OSError as e:
        sys.stderr.write( 'execution failed, command = ' + str(command)
                          + ', ' + str(e) + '\n' )
        exit( 1 )


def timediff_to_microseconds( t ):
    return t.microseconds + 1000000 * (t.seconds)


#
# scan all files to map
#
def add_path_timestamp_map( top_dir, map, file_pattern, file_number_index ):
    input_file_regexp = re.compile(FILE_PATTERN);
    for dirpath, child_dirs, child_files in os.walk( top_dir ):
        for f in child_files:
            m = input_file_regexp.match( f );
            if m == None:
                continue
            num = int(m.group(file_number_index), 10)

            fpath = os.path.join( dirpath, f )

            matched = False
            if exclude_globs != None:
                for g in exclude_globs:
                    if fnmatch.fnmatch( fpath, g ):
                        matched = True
                        break
                if matched:
                    continue

            time = datetime.datetime.fromtimestamp( os.stat(fpath)[ST_MTIME] )
            map[num] = [fpath, time]


#
# create path and timestamp map
#
path_timestamp_map = {}

pattern = re.compile(FILE_PATTERN);

for f in specified_dir_or_files:
    add_path_timestamp_map( f, path_timestamp_map, pattern, FILE_NUMBER_INDEX )


#
# main loop
#
if len(path_timestamp_map) == 0:
    exit()

if print_io:
    nullfile = None
else:
    nullfile = open( os.devnull )

first_print = True
start_wallclock_time = None
start_file_time = None

for item in sorted(path_timestamp_map.items()):
    (num, path, timestamp) = (item[0], item[1][0], item[1][1])

    #
    # set timestamps at first time
    #
    if start_wallclock_time == None or start_file_time == None:
        start_wallclock_time = datetime.datetime.now()
        start_file_time = timestamp


    #
    # set command
    #
    command = [DARUMA_CLIENT_COMMAND, '--host', host, '--port', str(port), path]
    if print_io:
        command.append( '--print-io' )

    #
    # set timestamp diff
    #
    file_time_diff = timestamp - start_file_time
    real_time_diff = datetime.datetime.now() - start_wallclock_time


    #
    # print messages
    #
    if verbose or do_wait:
        if not first_print:
            print( file=sys.stderr )
        first_print = False
    print( num, path, timestamp.isoformat(), file=sys.stderr )
    if verbose:
        print( 'command = [' + ' '.join(command) + ']', file=sys.stderr )
    if verbose or do_wait:
        print( 'walltime clock diff =', real_time_diff, file=sys.stderr )
        print( 'file timestamp diff =', file_time_diff, file=sys.stderr )


    #
    # wait
    #
    if do_wait:
        if fixed_wait_seconds != None:
            print( 'sleep ' + str(fixed_wait_seconds) + ' sec',
                   file=sys.stderr )
            time.sleep( fixed_wait_seconds )

            timestamp = datetime.datetime.now()
            real_time_diff = datetime.datetime.now() - start_wallclock_time

        else:
            while real_time_diff < file_time_diff:
                delta = file_time_diff - real_time_diff
                sleep_seconds = timediff_to_microseconds( delta ) / 1000000.0
                print( 'sleep ' + str(sleep_seconds) + ' sec', file=sys.stderr )
                time.sleep( sleep_seconds )

                timestamp = datetime.datetime.now()
                real_time_diff = datetime.datetime.now() - start_wallclock_time

    #
    # execute command
    #
    if print_io:
        execute_command( command )
    else:
        execute_command( command,
                         stdin=nullfile, stdout=nullfile, stderr=nullfile )


#
# do finalize
#
if nullfile != None:
    nullfile.close()
