#!/usr/bin/env python
#coding: utf8
#################################### IMPORTS ###################################

# Std Libs
import re

# Sublime Libs
import sublime
import sublime_plugin

#################################### HELPERS ###################################

def notify_nothing():
    sublime.status_message('Nothing to transpose')

def full_region(region):
    return (sublime.Region(region.begin(), region.begin() + 1)
            if region.empty() else region)

def perform_transposition(edit, view, trans, init_sel):
    " assumes trans is already reverse sorted sequence of regions"
    view.sel().subtract(init_sel)

    for i, (sel, substr) in enumerate(zip(trans,
                            reversed([view.substr(s) for s in trans]))):
        view.replace(edit, sel, substr)
        if not i:
            if init_sel.empty():
                view.sel().add(sublime.Region(init_sel.a + 1))
            else:
                view.sel().add(init_sel)

def transpose_selections(edit, view, can_transpose_words = False):
    for sel in view.sel():
        word_sel = view.word(sel)
        word_extents = (wb, we) = (word_sel.begin(), word_sel.end())
        transpose_words = sel.end() in word_extents

        #" wora! arst"
        if transpose_words and can_transpose_words:
            if sel.end() == we:
                next = view.find('\w', word_sel.end())
                if next is None: continue
                trans = [ view.word(next), word_sel ]
            else:
                if wb == 0: continue
                for pt in range(wb-1, -1, -1):
                    if re.match('\w', view.substr(pt)): break
                trans = [ word_sel, view.word(pt) ]
        else:
            p1 = max(0, sel.begin() - 1)
            character_behind_region = sublime.Region(p1)
            #" a!a"
            trans = [full_region(sel), full_region(character_behind_region)]

        perform_transposition(edit, view, trans, sel)

def rotate_selections(edit, view):
    for sel in view.sel():
        if sel.empty(): view.sel().add(view.word(sel))

    sels = list(reversed(view.sel()))

    strings = [ view.substr(s) for s in sels ]
    strings.append(strings.pop(0))

    for sel, substr in zip(sels, strings):
        view.replace(edit, sel, substr)

################################### COMMANDS ###################################

class Transpose(sublime_plugin.TextCommand):
    """
    - empty selection, cursor within a word: transpose characters
    - empty selection, cursor at the end of a word: transpose words
    - multiple selections, all empty: as above

    - multiple selections, at least one non-empty: rotate contents of selections
    (i.e., each selection takes on the contents of the selection before it)

    - single non-empty selection: do nothing

    """

    def run(self, edit, **kw):
        if not self.enabled(): return notify_nothing()

        view  = self.view
        sels  = view.sel()
        nsels = len(sels)

        if nsels > 1 and view.has_non_empty_selection_region():
            rotate_selections(edit, view)
        else:
            transpose_selections(edit, view)

    def enabled(self):
        sels = self.view.sel()
        return not (len(sels) == 1 and not sels[0].empty())