import os
import re
import hashlib
import tempfile
import sqlite3
import math
import random

from filestore import config


FILES_TABLE = "\
CREATE TABLE IF NOT EXISTS files (\
 hexdigest varchar,\
 filepath varchar,\
 created datetime DEFAULT current_timestamp);"
FILES_HEXDIGEST_IDX = "\
CREATE UNIQUE INDEX files_hexdigest_idx on files(hexdigest);"
LAST_ROWID = "SELECT MAX(rowid) from files;"
SELECT_FILEPATH = "SELECT filepath from files where hexdigest='%s'"
INSERT_HEXDIGEST = "INSERT INTO files (hexdigest) values ('%s')"
UPDATE_FILEPATH = "UPDATE files set filepath='%s' where hexdigest='%s'"
SELECT_FILEPATH_HEXDIGEST = "\
SELECT (hexdigest, filepath) from files where rowid=%s;"


class DataError(Exception): pass


class Files:
    def __init__(self, name=config.values['files']['name'], 
                   files_root=config.values['files']['root'],
                   hash_func=config.values['files']['hash_func'],
                   tune_size=config.values['files']['tune_size'],
                   assert_data_ok=config.values['files']['assert_data_ok']):
        """
        
        """
        if os.path.sep in name or '..' in name:
            raise ValueError('name can not contain .. or %s' % os.path.sep)
        self.hash_func = getattr(hashlib, hash_func)
        self.hash_len = len(hash_func('').hexdigest())
        self._root = os.path.join(files_root, name)
        #create our db if it doesn't exist already
        self._db = os.path.join(self._root, 'files.db')
        if not os.path.exists(self._db):
            #new repo...  lets create it
            if not os.path.exists(self._root):
                os.makedirs(self._root)
            con = sqlite3.connect(self._db)
            con.execute(FILES_TABLE)
            con.execute(FILES_HEXDIGEST_IDX)
            con.commit() 
            self.index = 0     
        self._folder_width = math.ceil(math.pow(tune_size, 1.0/3))
        self._folder_fmt = "%%0%sd" % len(str(self._folder_width))
        self._do_assert_data_ok = assert_data_ok

    def __len__(self):
        con = sqlite3.connect(self._db)
        c = con.execute(LAST_ROWID)
        res = c.fetchone()
        if res is None:
            i = 0
        else:
            i,_ = res
        return i
    
    _get_default = object()
    def get(self, hexdigest, d=self._get_default):
        try:
            return self[hexdigest]
        except KeyError:
            if d != self._get_default:
                return d
            raise

    def _assert_data_ok(self, hexdigest, filepath):
        if os.path.exists(filepath) is False:
            raise DataError('%s not found' % filepath)
        with open(filepath) as f:
            data = f.read()
        actual = self.hash_func(data).hexdigest()
        if hexdigest != actual:
            raise DataError('Expected %s, got %s' % (hexdigest, actual))

    def __getitem__(self, hexdigest):
        con = sqlite3.connect(self._db)
        c = con.execute(LAST_ROWID)
        c = con.execute(SELECT_FILEPATH % hexdigest)
        res = c.fetchone()
        if res is None:
            raise KeyError('%s not found' % hexdigest)
        filepath = os.path.join(self._root, res[0])
        if self._do_assert_data_ok:
            self._assert_data_ok(hexdigest, filepath)          
        return filepath

    def __setitem__(self, hexdigest, data):
        self.put(data, hexdigest=hexdigest)
    
    def put(self, data, hexdigest=None):
        if hexdigest is None:
            hexdigest = self.hash_func(data).hexdigest()
        else:
            hexdigest = hexdigest.lower()
            actual = self.hash_func(data).hexdigest()
            if hexdigest != actual:
                raise ValueError('actual hash %s != hexdigest %s' % \
                                    (actual, hexdigest))
        #check if it already exists
        try:
            self[hexdigest]
        except (KeyError, DataError):
            pass
        else:
            return
        #create our entry
        con = sqlite3.connect(self._db)
        c = con.execute(INSERT_HEXDIGEST % hexdigest) 
        rowid = c.lastrowid
        l1 = random.randint(0, self._folder_width)
        l2 = random.randint(0, self._folder_width)
        relroot = os.path.join(self._folder_fmt%l1, 
                            self._folder_fmt%l2)
        path = os.path.join(relroot, str(rowid))
        c = con.execute(UPDATE_FILEPATH % (path, hexdigest)) 
        dirpath = os.path.join(self._root, relroot)
        if not os.path.exists(dirpath):
            os.makedirs(dirpath)
        filepath = os.path.join(self._root, path)
        with open(filepath, 'wb') as f:
            f.write(data)
        con.commit()

    def __iter__(self):
        con = sqlite3.connect(self._db)
        c = con.execute("SELECT (hexdigest, filepath) from files")
        while True:
            rows = c.fetchmany()
            if not rows:
                break
            for row in rows:
                hexdigest, path = row
                filepath = os.path.join(self._root, path)
                if self._do_assert_data_ok:
                    self._assert_data_ok(hexdigest, filepath)
                yield (hexdigest, filepath)





@@ -683,16 +683,135 @@
 id=%25s;%22%0A
+SELECT_DIGESTS_LIMIT = %22SELECT hexdigest from files LIMIT %25s OFFSET %25s%22%0ASELECT_DIGESTS = %22SELECT hexdigest from files%22%0A
 %0A%0Aclass 
@@ -2600,20 +2600,27 @@
        r
-aise
+eturn None 
 %0A%0A    de
@@ -4912,49 +4912,22 @@
 ute(
-%22
 SELECT
- (hexdigest, filepath) from files%22
+_DIGESTS
 )%0A  
@@ -5082,20 +5082,17 @@
 gest
-, path
  = row
+%5B0%5D
 %0A   
@@ -5108,200 +5108,353 @@
     
-filepath = os.path.join(self._root, path)%0A                if self._do_assert_data_ok:%0A                    self._assert_data_ok(hexdigest, filepath)%0A                yield (hexdigest, filepath)%0A
+yield hexdigest%0A%0A    def select(self, a, b):%0A        %22%22%22select a range of hexdigest values to return%22%22%22%0A        con = sqlite3.connect(self._db)%0A        limit = b-a%0A        offset = a%0A        c = con.execute(SELECT_DIGESTS_LIMIT %25 (limit, offset))%0A        rows = c.fethall()%0A        hexdigests = %5Brow%5B0%5D for row in rows%5D%0A        return hexdigests
 %0A%0A%0A%0A

add select over digets. Return only hexdigest, return None when get has nothing