import os
import collections
import datetime

from morenines.util import get_hash


class Index(object):
    version = 1

    def __init__(self, root_path):
        self.headers = collections.OrderedDict()
        self.files = collections.OrderedDict()

        # Default to version in class; if reading file, this will get overwritten
        self.headers['version'] = Index.version
        self.headers['root_path'] = root_path

    def add(self, paths):
        for path in paths:
            # To hash the file, we need its absolute path
            abs_path = os.path.join(self.headers['root_path'], path)

            # We store the relative path in the index, not the absolute
            self.files[path] = get_hash(abs_path)

    def remove(self, paths):
        for path in paths:
            del self.files[path]

    def read(self, path):
        with open(path, 'r') as f:
            self.headers = parse_headers(f)

            if 'version' not in self.headers:
                raise Exception("Invalid file format: no version header")

            if self.headers['version'] != str(Index.version):
                raise Exception("Unsupported file format version: file is {}, parser is {}".format(self.headers['version'], Index.version))

            self.files = parse_files(f)

    def write(self, file_handle):
        # The date header is the moment the index is written to disk
        self.headers['date'] = datetime.datetime.utcnow().isoformat()

        # Write headers -- but sort the keys before writing
        for key in sorted(self.headers):
            file_handle.write("{}: {}\n".format(key, self.headers[key]))

        # Separate the headers from the files list with a blank line
        file_handle.write("\n")

        # Write files and hashes -- but sort the paths before writing
        for path in sorted(self.files):
            file_handle.write("{} {}\n".format(self.files[path], path))


##############################
# Index file parsing functions
##############################

def split_lines(lines, delim, num_fields):
    """Split each element in the sequence 'lines' into its component fields."""
    for line in lines:
        if line == '\n':
            return

        yield [field.strip() for field in line.split(delim, num_fields - 1)]

def parse_headers(file_):
    """Parse header lines of the form 'key: value'"""
    return {key: value for key, value in split_lines(file_, ':', 2)}

def parse_files(file_):
    """Parse file lines of the form 'HASHVALUE /path/to/file"""
    return {path:hash_ for hash_, path in split_lines(file_, ' ', 2)}

@@ -841,48 +841,15 @@
 lf, 
-path):%0A        with open(path, 'r') as f
+stream)
 :%0A  
@@ -850,28 +850,24 @@
 m):%0A        
-    
 self.headers
@@ -883,24 +883,25 @@
 headers(
-f)%0A%0A    
+stream)%0A%0A
         
@@ -934,20 +934,16 @@
 eaders:%0A
-    
         
@@ -1013,20 +1013,16 @@
         
-    
 if self.
@@ -1063,20 +1063,16 @@
 rsion):%0A
-    
         
@@ -1200,36 +1200,32 @@
 sion))%0A%0A        
-    
 self.files = par
@@ -1233,17 +1233,22 @@
 e_files(
-f
+stream
 )%0A%0A    d

Make Index.read() take a stream instead of a path