#!/usr/bin/python
# encoding: utf-8
"""
plotdevice.py

usage: plotdevice [-h] [-f] [-b] [--virtualenv PATH] [--export FILE]
               [--frames N or M-N] [--fps N] [--rate N] [--loop [N]] [--live]
               [--args [a [b ...]]]
               file

Run python scripts in PlotDevice.app or export graphics to a document (pdf/eps),
image (png/gif/jpg/tiff), or movie (mov/gif).

  Run a script:
    plotdevice script.pv

  Run fullscreen:
    plotdevice -f script.pv

  Save script's output to pdf:
    plotdevice script.pv --export output.pdf

  Create an animated gif that loops every 2 seconds:
    plotdevice script.pv --export output.gif --frames 60 --fps 30 --loop

  Create a sequence of numbered png files – one for each frame in the animation:
    plotdevice script.pv --export output.png --frames 10

  Create a 5 second long H.264 video at 2 megabits/sec:
    plotdevice script.pv --export output.mov --frames 150 --rate 2.0

Options:
  -h, --help          show this help message and exit
  -f                  run full-screen
  -b                  run PlotDevice in the background
  --virtualenv PATH   path to virtualenv whose libraries you want to use (this
                      should point to the top-level virtualenv directory; a
                      folder containing a lib/python2.7/site-packages
                      subdirectory)
  --export FILE       a destination filename ending in pdf, eps, png, tiff,
                      jpg, gif, or mov
  --cmyk              sets the output color mode for PDF, EPS, or TIFF exports
  --frames N or M-N   number of frames to render or a range specifying the
                      first and last frames (default "1-")
  --fps N             frames per second in exported video (default 30)
  --rate N            bitrate in megabits per second (video only)
  --loop [N]          number of times to loop an exported animated gif (omit N
                      to loop forever)
  --live              re-render graphics each time the file is saved
  --args [a [b ...]]  arguments to be passed to the script as sys.argv

PlotDevice Script File:
  file                the python script to be rendered
"""

import sys, os, re
import argparse
import json
import signal
from subprocess import Popen, PIPE
from os.path import exists, islink, dirname, abspath, realpath, join

def parse_args():
  parser = argparse.ArgumentParser(description=main.__doc__, add_help=False)
  o = parser.add_argument_group("Options", None)
  o.add_argument('-h','--help', action='help', help='show this help message and exit')
  o.add_argument('-f', dest='fullscreen', action='store_const', const=True, default=False, help='run full-screen')
  o.add_argument('-b', dest='activate', action='store_const', const=False, default=True, help='run PlotDevice in the background')
  o.add_argument('--virtualenv', metavar='PATH', help='path to virtualenv whose libraries you want to use (this should point to the top-level virtualenv directory; a folder containing a lib/python2.7/site-packages subdirectory)')
  o.add_argument('--export', metavar='FILE', help='a destination filename ending in pdf, eps, png, tiff, jpg, gif, or mov')
  o.add_argument('--frames', metavar='N or M-N', help='number of frames to render or a range specifying the first and last frames (default "1-")')
  o.add_argument('--fps', metavar='N', default=30, type=int, help='frames per second in exported video (default 30)')
  o.add_argument('--rate', metavar='N', default=1.0, type=float, dest='bitrate', help='bitrate in megabits per second (video only)')
  o.add_argument('--loop', metavar='N', default=0, nargs='?', const=-1, help='number of times to loop an exported animated gif (omit N to loop forever)')
  o.add_argument('--cmyk', action='store_const', const=True, default=False, help='convert colors to c/m/y/k during exports')
  o.add_argument('--live', action='store_const', const=True, help='re-render graphics each time the file is saved')
  o.add_argument('--args', nargs='*', default=[], metavar=('a','b'), help='arguments to be passed to the script as sys.argv')
  i = parser.add_argument_group("PlotDevice Script File", None)
  i.add_argument('file', help='the python script to be rendered')
  opts = parser.parse_args()

  if opts.virtualenv:
    libdir = '%s/lib/python2.7/site-packages'%opts.virtualenv
    if exists(libdir):
      opts.virtualenv = abspath(libdir)
    else:
      parser.exit(1, "bad argument [--virtualenv]\nvirtualenv site-packages dir not found: %s\n"%libdir)

  if opts.file:
    opts.file = abspath(opts.file)
    if not exists(opts.file):
      parser.exit(1, "file not found: %s\n"%opts.file)

  if opts.frames:
    try:
      frames = [int(f) if f else None for f in opts.frames.split('-')]
    except ValueError:
      parser.exit(1, 'bad argument [--frame]\nmust be a single integer ("42") or a hyphen-separated range ("33-66").\ncouldn\'t make sense of "%s"\n'%opts.frames)

    if len(frames) == 1:
      opts.first, opts.last = (1, int(frames[0]))
    elif len(frames) == 2:
      if frames[1] is not None and frames[1]<frames[0]:
        parser.exit(1, "bad argument [--frame]\nfinal-frame number is less than first-frame\n")
      opts.first, opts.last = frames
  else:
    opts.first, opts.last = (1, None)
  del opts.frames

  if opts.export:
    # screen out unsupported file extensions
    _, ext = opts.export.lower().rsplit('.',1)
    if ext not in ('pdf', 'eps', 'png', 'tiff', 'jpg', 'gif', 'mov'):
      parser.exit(1, 'bad argument [--export]\nthe output filename must end with a supported format:\n  pdf, eps, png, tiff, jpg, gif, or mov\n')

    # make sure the output path is sane
    if '/' in opts.export:
      export_dir = dirname(opts.export)
      if not exists(export_dir):
        parser.exit(1,'export directory not found: %s\n'%abspath(export_dir))
    opts.export = abspath(opts.export)

    # movies aren't allowed to be infinitely long (sorry)
    if opts.last is None and ext in ('mov','gif'):
      opts.first, opts.last = (1, 150)

    # if it's a multiframe pdf, check for a telltale "{n}" to determine whether
    # it's a `single' doc or a sequence of numbered pdf files
    opts.single = bool(opts.first<opts.last and ext=='pdf' and not re.search('{\d+}', opts.export))



  return opts

def module_root():
  parent = dirname(realpath(__file__)) if islink(__file__) else abspath(dirname(__file__))

  # if running from a virtualenv or site-packages dir
  for root in sys.path:
    if exists(join(root, 'plotdevice/__init__.py')):
      return root

  # if running from an app bundle
  appname = 'PlotDevice.app'
  if appname in parent:
    bundle = parent[:parent.index(appname)+len(appname)]
    return join(bundle, 'Contents/Resources/python')

  # if running from ./app/plotdevice in the source dist
  if exists(join(parent, 'PlotDevice-Info.plist')):
    return join(parent, '..')

  # ???
  print "Couldn't find the plotdevice module"
  sys.exit(1)

def main():
  """Run python scripts in a window/PlotDevice.app or export graphics to a
     document (pdf/eps), image (png/gif/jpg/tiff), or movie (mov/gif)."""

  # determine parameter values and command path
  opts = parse_args()
  root = module_root()
  env = dict(os.environ)

  # install a signal handler to catch ^c
  def cancel(*args):
    p.stdin.write("CANCEL\n")
    p.stdin.flush()
  signal.signal(signal.SIGINT, cancel)

  # launch the back-end of the console-runner and pipe in our params
  py_path = filter(None, env.get('PYTHONPATH','').split(':')) # BUG: should it be unfiltered?
  py_path.append(root)
  env['PYTHONPATH'] = ":".join(py_path)
  script = join(root, 'plotdevice/run/console.py')
  p = Popen(['/usr/bin/python', script], env=env, stdin=PIPE)
  p.stdin.write(json.dumps(vars(opts))+"\n")
  p.wait()

if __name__ == "__main__":
  main()
