#!/usr/bin/env python3

# Copyright (c) 2014 Martin Mahner <martin@mahner.org>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

"""
tab - Opens a new OS X Terminal window in the current directory and runs
an optional command in it.

If the current directory contains a file `.tabfile` this one is executed
instead (see below).

Download the latest version from: https://github.com/bartTC/tab

Installation:
-------------

Install it with pip: `pip install tab-osx` or if you prefer the manual way,
copy the `tab` file into your `/usr/local/bin` and make it executable:

    cp tab /usr/local/bin
    chmod +x /usr/local/bin/tab

Options:
--------

    (no arguments) Opens a new Terminal tab and changes to the current
                   directory.
    -n: Window name of  the new tab
    -w: Open a new window instead of a tab
    -p: Position of the new window on screen in the format x,y (e.g 400,200)
    -s: Size of the new window in the format w,h (e.g. 400,200)
    -t: Path to a tabfile to execute.

Example:
--------

    tab                                # Opens a new tab
    tab vim                            # Opens vim in a new tab
    tab -w -s 100,100 -p 400,700 vim   # Opens vim in a new window at the
                                       # specified position and size
    tab -t ~/Projects/project.tabfile  # Open the tabfile

Tabfile:
--------

You can specify multiple `tab` actions in one file, to quickly open multiple
tabs with commands. Tabfile is an INI file, each tab is seperated by a group
called either `tab:<n>` or `window:<n>`.

You can specify options as 'name:', 'size:', and 'position:'. All other
lines are commands which will be executed.

    [tab:1]                                     # Gulp Watcher
    source ~/myproject/bin/activate
    gulp watch

    [tab:2]                                     # Django Project
    source ~/myproject/bin/activate
    manage.py runserver                         # Start the server

    [tab:3]
    open http://127.0.0.1:8000/                 # Open web browser for above server
    clear                                       # and have a blank tab to work in

    [window:1]                                  # Vim Editor in a separate
    size: 200,400                               # window.
    position: 600,300
    vim
"""

import os
import sys
import re
import configparser
from time import sleep
from subprocess import Popen, PIPE
from optparse import OptionParser, OptionGroup

version = "1.4"
wd = os.getcwd()

description = """tab - Opens a new OS X Terminal window with the current
directory and runs an optional command in it. If the
current directory contains a file `.tabfile` this one is
executed instead (see file for directions)."""

usage = "Usage: %prog [options] cmd1 cmd2"

# ------------------------------------------------------------------------------

class Options(dict):
    """
    Allow dot notation for a dict so we're in line with the options
    coming from optpars.
    """
    def __getattr__(self, attr):
         return self.get(attr)

    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__

def osascript(scpt, args=[]):
     p = Popen(['osascript', '-'] + args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
     stdout, stderr = p.communicate(scpt.encode('utf-8'))
     return stdout, stderr

def get_config_items(config, groupname):
    options = Options()
    commands = []
    for n in config.items(groupname):
        if n[0] in ('name', 'size', 'position'):
            setattr(options[0], n[1])
        elif n[0] == None:
            continue
        else:
            commands.append(n[0])
    return options, commands

def opentab(window, scriptargs):
    if window:
        script = """
        tell application "Terminal"
            activate
            do script with command "cd {wd}"
            do script "clear" in window 1
            {name}
            {size}
            {position}
            {commands}
        end tell
        """
    else:
        script = """
        tell application "System Events"
            tell process "Terminal" to keystroke "t" using command down
        end
        tell application "Terminal"
            activate
            do script with command "cd {wd}" in window 1
            do script "clear" in window 1
            {name}
            {commands}
        end tell
        """
    stdout, stderr = osascript(script.format(**scriptargs))

    if stderr:
        sys.stderr.write('Error in Applescript: {}\n'.format(stderr))

def tab(options, args):
    # --------------------------------------------------------------------------
    # Tabfile
    # --------------------------------------------------------------------------
    tabfile = None
    local_tabfile = os.path.join(wd, '.tabfile')

    if options.tabfile:
        if not os.path.isfile(options.tabfile):
            sys.stderr.write('Tabfile "{}" does not exist.\n'.format(options.tabfile))
            sys.exit()
        tabfile = options.tabfile
    elif os.path.isfile(local_tabfile):
        tabfile = local_tabfile

    if tabfile:
        scripts = []
        config = configparser.ConfigParser(delimiters=('=',), allow_no_value=True)
        try:
            config.read(tabfile)
        except ConfigParser.ParsingError as e:
            sys.stderr.write('Tabfile seem to be broken. Error was: {}\n'.format(e))
            sys.exit()

        sys.stdout.write('Found tabfile in {}n'.format(tabfile))

        for groupname in config.sections():
            options, cmds = get_config_items(config, groupname)
            scriptargs = {
                'name': options.name and 'set custom title of window 1 to "{}"'.format(options.name) or '',
                'position': options.position and 'set position of window 1 to {{{}}}'.format(options.position) or '',
                'size': options.size and 'set size of window 1 to {{{}}}'.format(options.size) or '',
                'wd': wd,
            }
            if cmds:
                scriptargs['commands'] = '\n'.join(['do script with command "{}" in window 1'.format(a) for a in cmds])

            window = groupname.startswith('window')
            sleep(1)  # Applescript gets irritated if you do too much at once
            opentab(window, scriptargs)

    # --------------------------------------------------------------------------
    # Manual handling
    # --------------------------------------------------------------------------
    else:
        scriptargs = {
            'name': options.name and 'set custom title of window 1 to "{}"'.format(options.name) or '',
            'position': options.position and 'set position of window 1 to {{{}}}'.format(options.position) or '',
            'size': options.size and 'set size of window 1 to {{{}}}'.format(options.size) or '',
            'wd': wd,
            'commands': '\n'.join(['do script with command "{}" in window 1'.format(a) for a in args])
        }
        opentab(options.window, scriptargs)

# ------------------------------------------------------------------------------

def run_testsuite():
    sys.stdout.write('Test run, will open a couple of new windows and tabs\n')
    tab(Options(name="New empty tab"), [])
    tab(Options(name="Tab with ls"), ['ls'])
    tab(Options(name="1", position='100,100', size='300,100', window=True), [])
    tab(Options(name="2", position='400,100', size='300,100', window=True), [])
    tab(Options(name="3", position='100,200', size='300,100', window=True), [])
    tab(Options(name="4", position='400,200', size='300,100', window=True), [])
    tab(Options(name="5", position='100,300', size='600,300', window=True), ['ls -la'])

# ------------------------------------------------------------------------------

if __name__ == '__main__':
    parser = OptionParser(usage=usage, description=description, version="%prog " + version)
    parser.add_option("-n", dest="name", help="Name of the new tab: e.g. \"Vim Editor\"")
    parser.add_option("-t", dest="tabfile", help="Path to tabfile")

    group = OptionGroup(parser, "Window mode")
    group.add_option("-w", action="store_true", dest="window", help="Open a new window instead of a tab")
    group.add_option("-p", dest="position", help="Position of the new window in the format 400,200")
    group.add_option("-s", dest="size", help="Size of the new window in the format 800x,600")
    group.add_option("--testsuite", action="store_true", dest="test", help="Run a test suite to "
        "demonstrate and test various options.")
    parser.add_option_group(group)

    options, args = parser.parse_args()

    # Some input validation for -w and -p syntax
    if options.position and not re.match(r'\d+,\d+', options.position):
        sys.stderr.write("Error! The 'position' argument must be in the  format x,y (e.g 400,200)\n")
        sys.exit(1)

    if options.size and not re.match(r'\d+,\d+', options.size):
        sys.stderr.write("Error! The 'size' argument must be in the  format x,y (e.g 400,200)\n")
        sys.exit(1)

    # -----------------------------------------------------------------------------

    if options.test:
        run_testsuite()
    else:
        tab(options, args)

