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

import re
import urllib
import sys
import shutil
from config import *

class ResourceError(Exception):
    pass

class Resource:
    """
    A resource is either a local file or an url to a remote resource.
    """
    
    LOCAL, URL = range(2)
    _url = re.compile(r'^(http|ftp)')
    
    def __init__(self, uri, input = True, output = True):
        self.uri, self.input, self.output = self._input_output_uri(uri, input, output)
        self.filename = self.uri.split('/')[-1]
        if self._url.match(uri):
            self.type = self.URL
        else:
            self.type = self.LOCAL
    
    def __str__(self):
        return self.uri
    
    def _input_output_uri(self, uri, input = True, output = True):
        """
        Determines from the uri whether the resource should be an input resource,
        and output resource or an input-output resource.
        @param uri: the uri
        @param input: the input default value (if none specified)
        @param output: the output default value (if none specified)
        """
        uri = uri.strip()
        if uri.startswith(CFG_URI_INPUT):
            input, output = True, False
            uri = uri.strip(CFG_URI_INPUT).strip()
        if uri.startswith(CFG_URI_OUTPUT):
            input, output = False, True
            uri = uri.strip(CFG_URI_OUTPUT).strip()
        return uri, input, output
        
    def save_to(self, target_dir, filename = None):
        """
        Saves the resource to the target directory.
        If a file of that name already exists, tries to add '~<number>' at the end of the filename,
        to prevent overwriting.
        @param target_dir: the target directory to save the file to
        @param filename: the target filename, defaults to the resource original filename.
        @return: path to saved file
        """
        if filename is None:
            filename = self.filename
        try:
            target = os.path.join(target_dir, filename)
            if os.path.exists(target):
                newtarget = target
                i = 0
                while os.path.exists(newtarget):
                    i += 1
                    newtarget = target + '~%d' % i 
                target = newtarget
            if self.type == self.LOCAL:
                shutil.copy(self.uri, target)
            elif self.type == self.URL:
                urllib.urlretrieve(self.uri, target)
            return target
        except Exception, e:
            raise GridOcrError, "Couldn't save the file: %s" % e
    
class ResourceList:
    """
    A list of resources and options
    """
    OPTION, COMMAND, RESOURCE, COMMENT = range(4) # enum
    
    def __init__(self, file = None):
        self.list = []
        self.final_opts = {}
        if not file is None:
            self._read(file)

    def _read(self, file):
        """
        Reads the options and resources from a file.
        @param file: the file to read from. 
        """
        fd = open(file)
        lines = fd.read().split('\n')
        fd.close()
        for line in lines:
            line = line.strip()
            if len(line) == 0 or line[0] == CFG_COMMENT:
                self.add_comment(line)
            elif line[0] == CFG_OPT:
                opt = line.replace(CFG_OPT,"").strip().split(CFG_SEPARATOR)
                if len(opt) > 2:
                    print >> sys.stderr, "Unrecognized option/command: %s", line
                    continue
                elif len(opt) == 2:
                    self.add_option(opt[0].strip(), opt[1].strip())
                elif len(opt) == 1:
                    self.add_command(opt[0].strip())
            else:
                self.add_resource(line)

    def __iter__(self):
        """There may be no more than one iterator to a ResourceList at a given time!"""
        self.current_index = 0
        self.start_index = 0
        self.start_opts = {}
        self.cur_opts = {}
        return self
    
    def next(self):
        """Returns the next element of the resource/option list."""
        if len(self.list) <= self.current_index:
            raise StopIteration
        cur = self.list[self.current_index]
        self.current_index += 1
        if cur[0] == self.OPTION:
            name, value = cur[1]
            if not value is None:
                self.cur_opts[name] = value
            return self.next() # skip options
        elif cur[0] == self.COMMENT:
            return self.next() # skip comments
        return cur
    
    def add_comment(self, comment):
        """
        Adds a comment to the list.
        @param comment: the string of a comment.
        """
        comments = comment.split('\n')
        for comment in comments:
            if len(comment) > 0 and comment[0] != CFG_COMMENT:
                comment = '# ' + comment
            self.list.append((self.COMMENT, comment))
        
    
    def add_resource(self, uri, input = True, output = True):
        """
        Adds the resource at the end of list
        @param uri: the uri to the resource of the resource to be processed.
        """
        res = Resource(uri, input = input, output=output)
        self.list.append((self.RESOURCE, res))
    
    def add_input_resource(self, uri):
        """
        Adds the input-only resource at the end of list
        @param uri: the uri to the resource of the resource to be processed.
        """
        self.add_resource(uri, input = True, output = False)
        
    def add_output_resource(self, uri):
        """
        Adds the output-only resource at the end of list
        @param uri: the uri to the resource of the resource to be processed.
        """
        self.add_resource(uri, input = False, output = True)
    
    def add_command(self, cmd):
        """
        Adds a command to the list.
        It is meant to mean something in this place of the list.
        """
        self.list.append((self.COMMAND, cmd))
    
    def add_option(self, name, value):
        """
        Adds an option to the list. 
        The value of the option will be valid for all resources  added afterwards,
        until it is overriden by another assignment.
        @param name: the name of the option.
        @param value: the value of the option. 
        """
        self.list.append((self.OPTION,(name,value)))
        if not value is None:
            self.final_opts[name] = value 
    
    def get_resource_buffer(self):
        """
        Gets the current buffer with all its options as a new resource list.
        @return a new ResourceList containing only the current buffer
        """
        rl = ResourceList()
        for name, value in self.start_opts.items():
            if not value is None:
                rl.add_option(name, value)
        for type, value in self.list[self.start_index:self.current_index]:
            if type == ResourceList.OPTION:
                name, value = value
                rl.add_option(name, value)
            if type == ResourceList.RESOURCE:
                rl.add_resource(value.uri, value.input, value.output)
        return rl
        
    def reset_resource_buffer(self):
        """Clears the resource buffer, preserving all current options."""
        self.start_opts = self.cur_opts.copy()
        self.start_index = self.current_index
       
    def get_all_resources(self):
        """@return the list of all Resources in the list."""
        return [value for (type,value) in self.list if type == self.RESOURCE]
    
    def get_input_resources(self):
        """@return the list of all input Resources in the list."""
        return [value for (type,value) in self.list if type == self.RESOURCE and value.input == True]
    
    def get_output_resources(self):
        """@return the list of all output Resources in the list."""
        return [value for (type,value) in self.list if type == self.RESOURCE and value.output == True]
    
    def get_current_options(self):
        """
        THIS METHOD WILL ONLY WORK WHILE THE LIST IS BEING ITERATED!
        @return A dictionary of options and its values from the current point of iteration.
        """
        return self.cur_opts
    
    def get_current_option(self, name):
        """
        THIS METHOD WILL ONLY WORK WHILE THE LIST IS BEING ITERATED!
        @return Value of option "name" from the current point of iteration, or empty string if unset
        """
        if self.cur_opts.has_key(name):
            return self.cur_opts[name]
        else:
            return None
    
    def get_final_options(self):
        """@return A dictionary of options and its values from the end of the list."""
        return self.final_opts
    
    def get_final_option(self, name):
        """@return Value of option "name" from the end of list, or empty string if unset"""
        if self.final_opts.has_key(name):
            return self.final_opts[name]
        else:
            return None

    def write(self, file):
        """
        Writes the ResourceList to a file.
        @param file: the file to write to. 
        """
        try:
            fd = open(file, "w")
            for type, res in self.list:
                if type == self.OPTION:
                    self._write_option(fd, res)
                elif type == self.COMMAND:
                    self._write_command(fd, res)
                elif type == self.RESOURCE:
                    self._write_resource(fd, res)
                elif type == self.COMMENT:
                    fd.write(res + '\n') # comments are already put into the list with comment sign
            fd.close()
        except IOError, e:
            raise GridOcrError, "Couldn't write the resource buffer: %s" % e

    def _write_command(self, fd, command):
        """
        Writes an command as a line in the open file.
        @param fd: the open file descriptor
        @param command: the command to be written
        """
        fd.write(CFG_OPT + " %s\n" % name)
    
    def _write_option(self, fd, option):
        """
        Writes an option as a line in the open file.
        @param fd: the open file descriptor
        @param option: the option to be written
        """
        name, value = option
        fd.write(CFG_OPT + " %s = %s\n" % (name, value))
    
    def _write_resource(self, fd, res):
        """
        Writes a resource as a line in the open file.
        @param fd: the open file descriptor
        @param res: the resource to be written
        """
        if res.input and not res.output:
            prep = '< '
        elif res.output and not res.input:
            prep = '> '
        else:
            prep = ''
        fd.write(prep+res.uri+'\n')
