#!/usr/bin/env python
# encoding: utf-8

import subprocess, os, time, numpy, sys

# Global list of all/essential running processes
processes, necProcesses = [], []
# Command to run the rcssserver. Edit as needed.
SERVER_BIN = 'rcssserver'
# Command to run the monitor. Edit as needed.
MONITOR_BIN = 'soccerwindow2'

def launch(cmd, name = 'Unknown', necessary = True, supressOutput = True):
  """Launch a process.

  Appends to list of processes and (optionally) necProcesses if
  necessary flag is True.

  Returns: The launched process.
  """
  kwargs = {}
  if supressOutput:
    kwargs = {'stdout': open('/dev/null', 'w'),
              'stderr': open('/dev/null', 'w')}
  p = subprocess.Popen(cmd.split(' '), shell = False, **kwargs)
  processes.append(p)
  if necessary:
    necProcesses.append([p,name])
  return p

def main(args):
  """Sets up the teams, launches the server and monitor, starts the trainer.
  """
  if args.logging and not os.path.exists(args.logDir):
    os.makedirs(args.logDir)
  num_agents   = args.offenseAgents + args.defenseAgents
  binary_dir   = os.path.dirname(os.path.realpath(__file__))
  server_port  = args.port
  coach_port   = args.port + 1
  olcoach_port = args.port + 2
  serverCommand = os.path.join(binary_dir, SERVER_BIN)
  serverOptions = ' server::port=%i server::coach_port=%i ' \
                  'server::olcoach_port=%i server::coach=1 ' \
                  'server::game_logging=%i server::text_logging=%i ' \
                  'server::hfo_logging=%i server::hfo_log_dir=%s ' \
                  'server::game_log_dir=%s server::text_log_dir=%s '\
                  'server::synch_mode=%i server::hfo=1 ' \
                  'server::fullstate_l=%i server::fullstate_r=%i ' \
                  'server::coach_w_referee=1 server::hfo_max_trial_time=%i ' \
                  'server::hfo_max_trials=%i server::hfo_max_frames=%i ' \
                  'server::hfo_offense_on_ball=%i server::random_seed=%i ' \
                  'server::hfo_max_untouched_time=%i ' \
                  'server::say_msg_size=%i' \
                  %(server_port, coach_port, olcoach_port,
                    args.logging, args.logging, args.logging,
                    args.logDir, args.logDir, args.logDir,
                    args.sync, args.fullstate, args.fullstate,
                    args.maxFramesPerTrial, args.numTrials, args.numFrames,
                    args.offenseOnBall, args.seed, args.maxUntouchedTime,
                    args.messageSize)
                  # server::record_messages=on -- useful for debug
  try:
    # Launch the Server
    server = launch(serverCommand + serverOptions, name='server', supressOutput=True)
    time.sleep(0.2)
    assert server.poll() is None,\
      '[start.py] Failed to launch Server with command: \"%s\"' \
      '\n\nAnother rcssserver may be running on the same port?' \
      '\nTry: \"killall -9 rcssserver\"' \
      %(serverCommand + serverOptions)
    if not args.headless:
      monitorCommand = os.path.join(binary_dir, MONITOR_BIN)
      monitorOptions = ' --connect --port=%i'%(server_port)
      launch(monitorCommand + monitorOptions, name='monitor')
    # Launch the Trainer
    from Trainer import Trainer
    trainer = Trainer(args=args, server_port=server_port, coach_port=coach_port)
    trainer.initComm()
    # Run
    trainer.run(necProcesses, args.offenseTeam, args.defenseTeam)
  except KeyboardInterrupt:
    print '[start.py] Exiting for CTRL-C'
  finally:
    print '[start.py] Cleaning up server and other processes'
    for p in reversed(processes):
      try:
        p.terminate()
        time.sleep(0.1)
        p.kill()
      except:
        pass

def parseArgs():
  import argparse
  team_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'teams')
  installed_teams = os.listdir(team_dir)
  p = argparse.ArgumentParser(description='Start Half Field Offense.',
                              formatter_class=argparse.RawTextHelpFormatter)
  p.add_argument('--headless', dest='headless', action='store_true',
                 help='Run without a monitor.')
  p.add_argument('--trials', dest='numTrials', type=int, default=-1,
                 help='Number of trials to run.\n'\
                 'Negative values mean unlimited. Default: -1.')
  p.add_argument('--frames', dest='numFrames', type=int, default=-1,
                 help='Number of frames to run for.\n'\
                 'Negative values mean unlimited. Default: -1.')
  p.add_argument('--frames-per-trial', dest='maxFramesPerTrial', type=int,
                 default=1000, help='Max number of frames per trial.\n'\
                 'Negative values mean unlimited. Default: 1000.')
  p.add_argument('--untouched-time', dest='maxUntouchedTime', type=int,
                 default=100, help='Ends trial if ball is untouched for this long.\n'\
                'Negative values mean unlimited. Default: 100.')
  p.add_argument('--offense-agents', dest='offenseAgents', type=int, default=0,
                 help='Number of offensive agents. Default: 0.')
  p.add_argument('--defense-agents', dest='defenseAgents', type=int, default=0,
                 help='Number of defensive agents. Default: 0.')
  p.add_argument('--offense-npcs', dest='offenseNPCs', type=int, default=0,
                 help='Number of offensive uncontrolled players. Default: 0.')
  p.add_argument('--defense-npcs', dest='defenseNPCs', type=int, default=0,
                 help='Number of defensive uncontrolled players. Default: 0.')
  p.add_argument('--offense-team', dest='offenseTeam', type=str, default='base',
                 help='Offense team binary. Options: '+str(installed_teams)+'. Default: base.') 
  p.add_argument('--defense-team', dest='defenseTeam', type=str, default='base',
                 help='Defense team binary. Options: '+str(installed_teams)+'. Default: base.')
  p.add_argument('--no-sync', dest='sync', action='store_false', default=True,
                 help='Run server in non-sync mode.')
  p.add_argument('--port', dest='port', type=int, default=6000,
                 help='Agent server\'s port. Default: 6000.\n'\
                 'rcssserver, coach, and ol_coach will be '\
                 'incrementally allocated the following ports.')
  p.add_argument('--no-logging', dest='logging', action='store_false',
                 default=True, help='Disable rcssserver logging.')
  p.add_argument('--log-dir', dest='logDir', default='log/',
                 help='Directory to store logs. Default: log/')
  p.add_argument('--record', dest='record', action='store_true',
                 help='Record logs of states and actions.')
  p.add_argument('--offense-on-ball', dest='offenseOnBall', type=int,
                 default=0, help='Ball given to the player represented by the value.\n'\
                 'If value greater than the number of offense players, '\
                 'ball given to a random offense player.\n'\
                 'If value non-positive, ball is not given to any player.\n'\
                 'Default: 0.')
  p.add_argument('--fullstate', dest='fullstate', action='store_true',
                 help='Server provides full-state information to agents.')
  p.add_argument('--seed', dest='seed', type=int, default=-1,
                 help='Seed the server\'s RNG. Default: time.')
  p.add_argument('--message-size', dest='messageSize', type=int, default=1000,
                 help='Message size limit for communication')
  args = p.parse_args()
  if args.offenseAgents not in xrange(0, 11):
    p.error('argument --offense-agents: invalid choice: '\
            + str(args.offenseAgents) + ' (choose from [0-10])')
  if args.offenseNPCs not in xrange(0, 11):
    p.error('argument --offense-npcs: invalid choice: '\
            + str(args.offenseNPCs) + ' (choose from [0-10])')
  if args.defenseAgents not in xrange(0, 12):
    p.error('argument --defense-agents: invalid choice: '\
            + str(args.defenseAgents) + ' (choose from [0-11])')
  if args.defenseNPCs not in xrange(0, 12):
    p.error('argument --offense-npcs: invalid choice: '\
            + str(args.defenseNPCs) + ' (choose from [0-11])')
  if args.offenseAgents + args.offenseNPCs not in xrange(1, 11):
    p.error('Offense players (offense-agents + offense-npcs): '\
            'invalid choice: ' + str(args.offenseAgents + args.offenseNPCs) +\
            ' (choose from [1,10])')
  if args.defenseAgents + args.defenseNPCs not in xrange(0, 12):
    p.error('Defense players (defense-agents + defense-npcs): '\
            'invalid choice: ' + str(args.defenseAgents + args.defenseNPCs) +\
            ' (choose from [0,11])')
  if args.offenseTeam not in installed_teams:
    p.error('Unrecognized offense team: ' + str(args.offenseTeam))
  if args.defenseTeam not in installed_teams:
    p.error('Unrecognized defense team: ' + str(args.defenseTeam))
  return args

if __name__ == '__main__':
  main(parseArgs())