#!/usr/bin/env python

# http://gerrit-documentation.googlecode.com/svn/Documentation/2.2.2/cmd-
# query.html

import pkg_resources
import subprocess
from datetime import datetime
import simplejson as json
import time
import tempfile
import textwrap
import pydoc
import os


VALID_SCORES = ['-2', '-1', '-0', '0', '+0', '+1', '+2']


def normalize_score(score):
    if score not in VALID_SCORES:
        raise Exception('Invalid score %r' % score)
    if score in ('-0', '+0'):
        score = '0'
    return score


def arg_encode(arg):
    # We're going to end up with the original value enclosed by single quotes,
    # excepting the single quotes in the original value; those will be encoded
    # in double quotes. Yes, kinda awful to explain.
    # test = 'test'
    # "test" = '"test"'
    # 'test' = "'"'test'"'"
    # "it's" = '"it'"'"'s"'
    arg = "'" + arg.replace("'", "'\"'\"'") + "'"
    if arg.startswith("''"):
        arg = arg[2:]
    if arg.endswith("''"):
        arg = arg[:-2]
    return arg


def get_message(message):
    if not message:
        editor = os.environ.get(
            'FGERRIT_EDITOR', os.environ.get('EDITOR', 'vi'))
        with tempfile.NamedTemporaryFile() as fp:
            p = subprocess.Popen('%s %s' % (editor, fp.name), shell=True)
            retval = p.wait()
            if retval != 0:
                raise Exception('Error on editor exit code %d' % retval)
            message = fp.read().strip()
        if not message:
            raise Exception('Abort, no message')
    if message == '-':
        message = ''
    return message


class FGerrit(object):

    def __init__(self, ssh_user, ssh_host, project, ssh_port=29418,
                 status="open"):
        self.ssh_user = ssh_user
        self.ssh_host = ssh_host
        self.ssh_port = ssh_port
        self.project = project
        self.status = status
        term_info = os.popen('stty size', 'r').read().split()
        self.term_rows = int(term_info[0])
        self.full_width = int(term_info[1])

    def _cprint(self, output):
        """either print output or invoke pager"""
        if self.term_rows < sum([len(i.split('\n')) for i in output]):
            pydoc.pager('\n'.join(output).decode('utf8').encode('utf8'))
        else:
            print '\n'.join(output).decode('utf8').encode('utf8')

    def _conv_ts(self, timestamp, terse=False):
        if terse:
            when = time.time() - timestamp
            if when < 60:
                return '%4.1fs' % when
            elif when < 3600:
                return '%4.1fm' % (when / 60)
            elif when < 86400:
                return '%4.1fh' % (when / 3600)
            else:
                return '%4.1fd' % (when / 86400)
        else:
            return datetime.fromtimestamp(int(timestamp))

    def _run_query(self, qargs, plain=False):
        if not plain:
            sshcmd = 'ssh -p %d %s@%s "gerrit query --format=JSON %s"' % \
                (self.ssh_port, self.ssh_user, self.ssh_host, qargs)
        else:
            sshcmd = 'ssh -p %d %s@%s "gerrit query --format=TEXT %s"' % \
                (self.ssh_port, self.ssh_user, self.ssh_host, qargs)
        tmp = tempfile.TemporaryFile()
        p = subprocess.Popen(sshcmd, shell=True, stdout=tmp,
                             stderr=subprocess.STDOUT)
        retval = p.wait()
        tmp.seek(0)
        if retval != 0:
            raise Exception('Error on ssh to gerrit %s' % tmp.readlines())
        if not plain:
            result = []
            for line in tmp.readlines():
                result.append(json.loads(line))
            retval = p.wait()
            return [x for x in result if 'status' in x]
        else:
            return " ".join(tmp.readlines())

    def _run_cmd(self, cargs):
        sshcmd = "ssh -p %d %s@%s %s" % (
            self.ssh_port, self.ssh_user, self.ssh_host,
            arg_encode('gerrit ' + cargs))
        tmp = tempfile.TemporaryFile()
        p = subprocess.Popen(sshcmd, shell=True, stdout=tmp,
                             stderr=subprocess.STDOUT)
        retval = p.wait()
        tmp.seek(0)
        if retval != 0:
            raise Exception('Error on ssh to gerrit %s' % tmp.readlines())
        return " ".join(tmp.readlines())

    def list_reviews(self):
        return self._run_query(
            'status:%s project:%s --current-patch-set' % (self.status,
                                                          self.project))

    def _parse_approvals(self, review):
        retval = [' ', ' ', ' ']
        for i in review.get('currentPatchSet', {}).get('approvals', []):
            typ = i['type']
            idx = {'VRIF': 0, 'CRVW': 1, 'APRV': 2}.get(typ)
            if idx is not None:
                val = int(i['value'])
                if val < 0:
                    retval[idx] = '-'
                elif typ == 'CRVW':
                    if val > 1 and retval[idx] == ' ':
                        retval[idx] = '+'
                elif val > 0 and retval[idx] == ' ':
                    retval[idx] = '+'
        return retval

    def get_review(self, review_id, comments=False, text=False):
        """Either a short id (5264) or long hash"""
        if comments:
            return self._run_query('commit:%s --current-patch-set --comments '
                                   '--commit-message' % review_id, plain=text)
        else:
            return self._run_query(review_id, plain=text)

    def delete_change(self, patchset):
        payload = "review %s --delete" % patchset
        return self._run_cmd(payload)

    def abandon_change(self, patchset):
        payload = "review %s --abandon" % patchset
        return self._run_cmd(payload)

    def restore_change(self, patchset):
        payload = "review %s --restore" % patchset
        return self._run_cmd(payload)

    def post_message(self, review_id, message):
        payload = "review %s --message=%s" % (review_id, arg_encode(message))
        return self._run_cmd(payload)

    def code_review(self, review_id, score, message=None):
        score = normalize_score(score)
        payload = 'review %s --code-review %s' % (review_id, score)