from __future__ import with_statement

import marshal
import sys

from .xdelta import create_xdelta


class Differ(object):

    USES_XDELTA = False

    class BasenameMismatch(Exception):
        pass

    PRIORITY = sys.maxint

    @classmethod
    def select_differ(cls, old, new, use_xdelta):
        if old.basename() != new.basename():
            raise cls.BasenameMismatch

        def traverse(clazz):
            yield clazz
            for sc in clazz.__subclasses__():
                for c in traverse(sc):
                    if not use_xdelta and c.USES_XDELTA:
                        continue
                    yield c

        for candidate in sorted(
            list(traverse(Differ)), key=lambda differ: differ.PRIORITY
        ):
            if candidate.applies(old, new):
                return candidate(old, new)

    @classmethod
    def applies(cls, old, new):  # pragma: no cover
        """
        the base implementation never
        applies.
        """
        return False

    def __init__(self, old, new):
        self.old, self.new = old, new

    def delta_file_into(self, dest):
        with dest.open("wb") as outf:
            with self.new.open("rb") as inf:
                outf.write(inf.read())
        return dest

    def are_different(self):
        return self.old.info().mode != self.new.info().mode


class SymlinkDiffer(Differ):

    PRIORITY = 100

    USES_XDELTA = False

    @classmethod
    def applies(cls, old, new):
        return old.islink() or new.islink()

    def are_different(self):
        return (
            self.old.islink() != self.new.islink()
            or self.old.readlink() != self.new.readlink()
            or super(SymlinkDiffer, self).are_different()
        )

    def delta_file_into(self, dest):
        if self.new.islink():
            self.new.readlink().symlink(dest)
            return dest
        else:
            return super(SymlinkDiffer, self).delta_file_into(dest)


class MD5Differ(Differ):

    PRIORITY = sys.maxint - 1

    def are_different(self):
        if self.old.md5() != self.new.md5():
            return True
        return super(MD5Differ, self).are_different()

    @classmethod
    def applies(cls, old, new):
        """
        If nothing else, we compare by MD5
        """
        return True


class ALPDiffer(Differ):

    PRIORITY = 100

    USES_XDELTA = True

    @classmethod
    def applies(cls, old, new):
        return old.splitext()[1] == ".alp"

    def are_different(self):
        if self.old.md5() != self.new.md5():
            return True
        return super(ALPDiffer, self).are_different()

    def delta_file_into(self, dest):
        xdelta = dest + ".xdelta"
        create_xdelta(self.old, self.new, xdelta)
        return xdelta
