import sys
import os
import re
import logging

from libcan import text_encode, fs_encode, fs_decode
from libcan.file import File


class FilesystemReader(object):
    """Walk recursively through a directory tree located at root, 
       while excluding filenames that match the regular expression
       excludes.
    """

    null_regex = re.compile("^$") # This will never match.

    def __init__(self, root, paths, excludes=None, max_size=None, one_file_system=False, exclude_tag=None):
        self.root = root
        self.paths = sorted(paths)
        self.max_size = max_size
        self.one_file_system = one_file_system
        self.exclude_tag = exclude_tag

        if excludes is None:
            self.excludes = self.null_regex
        else:
            self.excludes = excludes

    def __iter__(self):
        directory = self.root.rstrip("/") + "/"

        for path in self.paths:
            if self.excludes.match(path):
                logging.debug("X %s", path)
                continue

            if self.one_file_system and os.path.ismount(os.path.join(self.root, path)):
                # -l/--one-file-system
                logging.debug("x %s", path)
                continue

            try:
                file = File.from_name(self.root, path)
            except EnvironmentError, e:
                logging.warn("%s: %s", e.filename, e.strerror)
                continue

            if file is None:
                logging.warn("%s: cannot store unsupported file", path)
                continue

            if self.max_size is not None and file.size > self.max_size:
                # -M/--max-size
                logging.debug("x %s", path)
                continue

            yield file

            if file.isdir():
                exclude = False
                if self.exclude_tag is not None:
                    # --exclude-tag
                    tag_file = os.path.join(self.root, path, self.exclude_tag)
                    if os.path.exists(tag_file):
                        if self.exclude_tag == "CACHEDIR.TAG":
                            try:
                                exclude = open(tag_file).read(43) == "Signature: 8a477f597d28d172789f06886806bc55"
                            except EnvironmentError, e:
                                logging.warn("%s: %s", e.filename, e.strerror)
                                exclude = False
                        else:
                            exclude = True

                if exclude:
                    logging.debug("x %s/...", path)
                    names = [os.path.join(path, self.exclude_tag)]
                else:
                    try:
                        # XXX If the filesystem encoding is not set right some names
                        # in the result from os.listdir() may be no unicode strings.
                        # We do en/decoding ourselves.
                        names = []
                        for name in os.listdir(fs_encode(os.path.join(self.root, path))):
                            try:
                                names.append(os.path.join(path, fs_decode(name)))
                            except UnicodeError, e:
                                logging.warn(e)
                                continue
                    except EnvironmentError, e:
                        logging.warn("%s: %s", e.filename, e.strerror)
                        continue

                for file in FilesystemReader(self.root, names, self.excludes, self.max_size, self.one_file_system, self.exclude_tag):
                    yield file

    def close(self):
        pass


class FileReader(object):
    """Read a list of files from a file or stdin.
    """

    def __init__(self, root, filename):
        self.root = root
        self.filename = filename

        if filename == "-":
            self.fileobj = sys.stdin
        else:
            self.fileobj = file(filename)

    def __iter__(self):
        filelist = set()

        for name in self.fileobj:
            name = name.strip()
            if not name:
                continue

            name = text_decode(name)

            path = os.path.normpath(os.path.join(self.root, name))
            if not path.startswith(self.root):
                logging.error("%r is not inside %r", name, self.root)
                continue

            filelist.add(name)

        for name in sorted(filelist):
            try:
                file = File.from_name(self.root, name)
            except EnvironmentError, e:
                logging.warn("%s: %s", e.filename, e.strerror)
                continue

            if file is None:
                logging.warn("%s: cannot store unsupported file", name)
                continue

            yield file

    def close(self):
        pass


