#! /usr/bin/env python
#
#    SVN browsing routines for SPLAIN
#    Copyright (C) 2006  Michael Urman
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of version 2 of the GNU General Public License as
#    published by the Free Software Foundation.
#

import pysvn

import pysvn
import posixpath
import datetime

def revision(rev):
    if hasattr(rev, "number"):
        return rev
    if isinstance(rev, (int, long)):
        rev = str(rev)
    if rev.lower() == "head":
        return pysvn.Revision(pysvn.opt_revision_kind.head)
    if rev.lower() == "base":
        return pysvn.Revision(pysvn.opt_revision_kind.base)
    if rev.isdigit():
        return pysvn.Revision(pysvn.opt_revision_kind.number, int(rev))

    assert not rev
    return pysvn.Revision(pysvn.opt_revision_kind.working)

class SvnItem(object):
    def __init__(self, svn, info=None):
        self.svn = svn
        self.info = info

    @property
    def revision(self):
        get = self.info.get
        rev = get("last_changed_rev") or get("created_rev") or get("rev") or get("revision")
        try:
            kind = rev.kind
        except AttributeError:
            return str(self.info)
        else:
            if kind == pysvn.opt_revision_kind.head: return "HEAD"
            if kind == pysvn.opt_revision_kind.number: return rev.number
        raise ValueError(kind)

    @property
    def revstr(self):
        return str(self.revision)

    @property
    def raw_time(self, dt=datetime.datetime):
        get = self.info.get
        return dt.fromtimestamp(int(get("time") or get("date") or 0))

    @property
    def timestamp(self, zero=datetime.datetime.fromtimestamp(0)):
        when = self.raw_time
        if when == zero: return ""
        else: return when.isoformat(" ")

    @property
    def time(self, t=datetime.time):
        return self.raw_time.strftime("%H:%M")

    @property
    def date(self, d=datetime.date):
        return self.raw_time.strftime("%Y-%m-%d")

class SvnFSItem(SvnItem):

    @property
    def size(self):
        return ""

    @property
    def author(self):
        get = self.info.get
        return get("last_author") or get("last_changed_author")

    @property
    def url(self):
        get = self.info.get
        return get("URL") or get("name") or get("path")

    @property
    def name(self):
        return posixpath.basename(self.url)

    @property
    def path(self):
        path = self.url
        if path.startswith(self.svn.url):
            path = path[len(self.svn.url):]
        if path.startswith("/"):
            path = path[1:]
        return path

    @property
    def properties(self):
        rev = revision(self.revision)
        try:
            props = pysvn.Client().proplist(self.url, rev, recurse=False)
        except pysvn.ClientError: # see mu:/releases/cankiri-0.1/cankiri.py
            props = None

        if props:
            return props[0][1]
        else:
            return {}

class File(SvnFSItem):
    kind = "file"

    def __iter__(self):
        return iter(pysvn.Client().cat(self.url, self.info["rev"]).splitlines(True))

    @property
    def size(self, sizes=("", "K", "M", "G", "T", "P")):
        size = self.info["size"]
        for marker in sizes:
            if size < 900: break
            size = round(size / 1024.0, 1)
        return "%0s %sB" % (size, marker)

class Folder(SvnFSItem):
    kind = "dir"

    def __iter__(self):
        for entry in pysvn.Client().ls(self.url, self.info["rev"], recurse=False):
            yield self.svn.get(entry["name"], entry=entry)

    def files(self):
        for entry in pysvn.Client().ls(self.url, self.info["rev"], recurse=False):
            if str(entry["kind"]) == "file":
                yield self.svn.get(entry["name"], entry=entry)

    def folders(self):
        for entry in pysvn.Client().ls(self.url, self.info["rev"], recurse=False):
            if str(entry["kind"]) == "dir":
                yield self.svn.get(entry["name"], entry=entry)

    def parent(self):
        parenturl = posixpath.dirname(self.url)
        try:
            entry = pysvn.Client().info2(parenturl, self.info["rev"], recurse=False)[0][1]
        except pysvn.ClientError:
            return
        else:
            entry["URL"] = self.url + "/.."
            yield Folder(self.svn, entry)

class Revision(SvnItem):
    @property
    def author(self):
        return self.info["author"]

    @property
    def message(self):
        return self.info["message"]

    def __iter__(self):
        for change in self.info["changed_paths"]:
            yield FileRevision(self.svn, change, self.info["revision"])

    def __str__(self):
        return self.revision

class FileRevision(SvnFSItem):
    kind = "file"
    def __init__(self, svn, info, rev):
        SvnFSItem.__init__(self, svn, info)
        self.info["revision"] = rev

    @property
    def action(self):
        return self.info["action"]

    def __iter__(self):
        url = self.svn.url + self.url[1:]
        base = revision(str(int(self.revision) - 1))
        rev = self.info["revision"]
        if self.action == "M":
            from tempfile import NamedTemporaryFile
            temp = NamedTemporaryFile()
            diff = pysvn.Client().diff(temp.name, url, revision1=base, revision2=rev, diff_deleted=False)
            return iter(diff.splitlines(True))
        elif self.action == "D":
            return iter(pysvn.Client().cat(url, base).splitlines(True))
        else:
            return iter(pysvn.Client().cat(url, rev).splitlines(True))

class Repository(object):
    def __init__(self, url, rev=None):
        if not url.endswith("/"):
            url = url + "/"
        self.url = url
        self.rev = rev or revision("head")

    def get(self, *path, **kw):
        url = posixpath.join(self.url, *path)
        rev = revision(kw.pop("rev", self.rev))
        entry = kw.pop("entry", None)
        if entry is None:
            client = pysvn.Client()
            entry = client.info2(url, rev, recurse=False)[0][1]

        if entry["kind"] == pysvn.node_kind.dir:
            return Folder(self, entry)
        elif entry["kind"] == pysvn.node_kind.file:
            return File(self, entry)

        raise KeyError("URL %s not supported" % url)

    def revision(self, rev=None):
        rev = revision(rev or self.rev)
        messages = pysvn.Client().log(self.url, rev, discover_changed_paths=True, limit=1)
        if len(messages) == 0:
            return None
        return Revision(self, messages[0])

    def log(self, limit):
        messages = pysvn.Client().log(self.url, discover_changed_paths=True, limit=limit)
        for message in messages:
            yield Revision(self, message)
