# -*- coding: utf-8 -*-

import os
import re

from glob import glob
from os.path import basename, dirname, join
from string import digits
from subprocess import Popen, PIPE

from module.plugins.internal.Extractor import Extractor, ArchiveError, CRCError, PasswordError
from module.utils import save_join, decode


def renice(pid, value):
    if os.name != "nt" and value:
        try:
            Popen(["renice", str(value), str(pid)], stdout=PIPE, stderr=PIPE, bufsize=-1)
        except:
            print "Renice failed"


class UnRar(Extractor):
    __name__    = "UnRar"
    __version__ = "1.01"

    __description__ = """Rar extractor plugin"""
    __license__     = "GPLv3"
    __authors__     = [("Walter Purcaro", "vuolter@gmail.com")]


    CMD = "unrar"

    EXTENSIONS = ["rar", "zip", "cab", "arj", "lzh", "tar", "gz", "bz2", "ace", "uue", "jar", "iso", "7z", "xz", "z"]


    #@NOTE: there are some more uncovered rar formats
    re_rarpart = re.compile(r'(.*)\.part(\d+)\.rar$', re.I)
    re_rarfile = re.compile(r'.*\.(rar|r\d+)$', re.I)

    re_filelist  = re.compile(r'(.+)\s+(\d+)\s+(\d+)\s+|(.+)\s+(\d+)\s+\d\d-\d\d-\d\d\s+\d\d:\d\d\s+(.+)')
    re_wrongpwd  = re.compile(r'password', re.I)
    re_wrongcrc  = re.compile(r'encrypted|damaged|CRC failed|checksum error', re.I)


    @classmethod
    def checkDeps(cls):
        if os.name == "nt":
            cls.CMD = join(pypath, "UnRAR.exe")
            p = Popen([cls.CMD], stdout=PIPE, stderr=PIPE)
            p.communicate()
        else:
            try:
                p = Popen([cls.CMD], stdout=PIPE, stderr=PIPE)
                p.communicate()

            except OSError:
                # fallback to rar
                cls.CMD = "rar"
                p = Popen([cls.CMD], stdout=PIPE, stderr=PIPE)
                p.communicate()

        return True


    @classmethod
    def isArchive(cls, file):
        f = basename(file).lower()
        return any(f.endswith('.%s' % ext) for ext in cls.EXTENSIONS)


    @classmethod
    def getTargets(cls, files_ids):
        targets = []

        for file, id in files_ids:
            if not cls.isArchive(file):
                continue

            m = cls.re_rarpart.findall(file)
            if m:
                # only add first parts
                if int(m[0][1]) == 1:
                    targets.append((file, id))
            else:
                targets.append((file, id))

        return targets


    def check(self, out="", err=""):
        if not out or not err:
            return

        if err.strip():
            if self.re_wrongpwd.search(err):
                raise PasswordError

            elif self.re_wrongcrc.search(err):
                raise CRCError

            else:  #: raise error if anything is on stderr
                raise ArchiveError(err.strip())

        # output only used to check if passworded files are present
        for attr in self.re_filelist.findall(out):
            if attr[0].startswith("*"):
                raise PasswordError


    def verify(self):
        p = self.call_cmd("l", "-v", self.file, password=self.password)

        self.check(*p.communicate())

        if p and p.returncode:
            raise ArchiveError("Process terminated")

        if not self.list():
            raise ArchiveError("Empty archive")


    def isPassword(self, password):
        if isinstance(password, basestring):
            p = self.call_cmd("l", "-v", self.file, password=password)
            out, err = p.communicate()

            if not self.re_wrongpwd.search(err):
                return True

        return False


    def repair(self):
        p = self.call_cmd("rc", self.file)
        out, err = p.communicate()

        if p.returncode or err.strip():
            p = self.call_cmd("r", self.file)
            out, err = p.communicate()

            if p.returncode or err.strip():
                return False
            else:
                self.file = join(dirname(self.file), re.search(r'(fixed|rebuild)\.%s' % basename(self.file), out).group(0))

        return True


    def extract(self, progress=lambda x: None):
        self.verify()

        progress(0)

        command = "x" if self.fullpath else "e"

        p = self.call_cmd(command, self.file, self.out, password=self.password)

        renice(p.pid, self.renice)

        progressstring = ""
        while True:
            c = p.stdout.read(1)
            # quit loop on eof
            if not c:
                break
            # reading a percentage sign -> set progress and restart
            if c is '%':
                progress(int(progressstring))
                progressstring = ""
            # not reading a digit -> therefore restart
            elif c not in digits:
                progressstring = ""
            # add digit to progressstring
            else:
                progressstring += c

        progress(100)

        self.files = self.list()

        # retrieve stderr
        self.check(err=p.stderr.read())

        if p.returncode:
            raise ArchiveError("Process terminated")


    def getDeleteFiles(self):
        if ".part" in basename(self.file):
            return glob(re.sub("(?<=\.part)([01]+)", "*", self.file, re.I))

        # get files which matches .r* and filter unsuited files out
        parts = glob(re.sub(r"(?<=\.r)ar$", "*", self.file, re.I))

        return filter(lambda x: self.re_rarfile.match(x), parts)


    def list(self):
        command = "vb" if self.fullpath else "lb"

        p = self.call_cmd(command, "-v", self.file, password=self.password)
        out, err = p.communicate()

        if err.strip():
            self.m.logError(err)
            if "Cannot open" in err:
                return list()

        if p.returncode:
            self.m.logError("Process terminated")
            return list()

        result = set()

        for f in decode(out).splitlines():
            f = f.strip()
            result.add(save_join(self.out, f))

        return list(result)


    def call_cmd(self, command, *xargs, **kwargs):
        args = []

        # overwrite flag
        if self.overwrite:
            args.append("-o+")
        else:
            args.append("-o-")
            if self.delete:
                args.append("-or")

        for word in self.excludefiles:
            args.append("-x%s" % word.strip())

        # assume yes on all queries
        args.append("-y")

        # set a password
        if "password" in kwargs and kwargs['password']:
            args.append("-p%s" % kwargs['password'])
        else:
            args.append("-p-")

        if self.keepbroken:
            args.append("-kb")

        # NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue
        call = [self.CMD, command] + args + list(xargs)
        self.m.logDebug(" ".join(call))

        return Popen(call, stdout=PIPE, stderr=PIPE)