#!/usr/bin/env 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
"""
from __future__ import print_function
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 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, 'info.plist')):
    return join(parent, '..')

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

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))

  opts.site = module_root()
  return opts

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()

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

  # launch the back-end of the console-runner and pipe in our params
  prefix = getattr(sys, 'real_prefix', getattr(sys, 'base_prefix', None))
  suffix = '/bin/python' + '3' if sys.version_info >= (3,) else ''
  python_cmd = prefix+suffix if prefix else sys.executable
  console_py = join(opts.site, 'plotdevice/run/console.py')

  p = Popen([python_cmd, console_py], stdin=PIPE)
  opts = (json.dumps(vars(opts))+"\n").encode('utf-8')
  p.stdin.write(opts)
  p.stdin.flush()
  p.wait()

if __name__ == "__main__":
  main()
