import os
import sqlite3

HASH_TYPE_MD5 = 0 # 16 bytes long
HASH_TYPE_SHA1 = 1 # 20 bytes long
HASH_TYPE_SHA256 = 2 # 32 bytes long
HASH_TYPE_SHA512 = 3 # 64 bytes long

# Please feel free to use this file however you wish.
# None of this is thread-safe, though, so don't try to do anything clever.

# A rating for hydrus is a float from 0.0 to 1.0
# dislike/like are 0.0 and 1.0
# numerical are fractions between 0.0 and 1.0
# for a four-star rating that allows 0 stars, the 5 possibles are: 0.0, 0.25, 0.5, 0.75, 1.0
# for a three-star rating that does not allow 0 stars, the three possibles are: 0.0, 0.5, 1.0
# in truth, at our level:
    # a five-star rating that does allow stars is a six-star rating
    # a ten-star rating that does not allow stars is a ten-star rating

# If you want to make a new rating archive for use in hydrus, you want to do something like:

# import HydrusRatingArchive
# hra = HydrusRatingArchive.HydrusRatingArchive( 'my_little_archive.db' )
# hra.SetHashType( HydrusRatingArchive.HASH_TYPE_MD5 )
# hra.SetNumberOfStars( 5 )
# hra.BeginBigJob()
# for ( hash, rating ) in my_rating_generator: hra.AddRating( hash, rating )
# hra.CommitBigJob()
# del hra


# If you are only adding a couple ratings, you can exclude the BigJob stuff. It just makes millions of sequential writes more efficient.


# Also, this manages hashes as bytes, not hex, so if you have something like:

# hash = ab156e87c5d6e215ab156e87c5d6e215

# Then go hash = bytes.fromhex( hash ) before you pass it to Add/Get/Has/SetRating


# And also feel free to contact me directly at hydrus.admin@gmail.com if you need help.

class HydrusRatingArchive( object ):
    
    def __init__( self, path : str ) -> None:
        
        self._path = path
        
        if not os.path.exists( self._path ): create_db = True
        else: create_db = False
        
        self._InitDBConnection()
        
        if create_db: self._InitDB()
        
    
    def _InitDB( self ) -> None:
        
        self._c.execute( 'CREATE TABLE hash_type ( hash_type INTEGER );', )
        
        self._c.execute( 'CREATE TABLE number_of_stars ( number_of_stars INTEGER );', )
        
        self._c.execute( 'CREATE TABLE ratings ( hash BLOB PRIMARY KEY, rating REAL );' )
        
    
    def _InitDBConnection( self ) -> None:
        
        self._db = sqlite3.connect( self._path, isolation_level = None, detect_types = sqlite3.PARSE_DECLTYPES )
        
        self._c = self._db.cursor()
        
    
    def BeginBigJob( self ) -> None:
        
        self._c.execute( 'BEGIN IMMEDIATE;' )
        
    
    def CommitBigJob( self ) -> None:
        
        self._c.execute( 'COMMIT;' )
        self._c.execute( 'VACUUM;' )
        
    
    def DeleteRating( self, hash ) -> None:
        
        self._c.execute( 'DELETE FROM ratings WHERE hash = ?;', ( sqlite3.Binary( hash ), ) )
        
    
    def GetHashType( self ):
        
        result = self._c.execute( 'SELECT hash_type FROM hash_type;' ).fetchone()
        
        if result is None:
            
            result = self._c.execute( 'SELECT hash FROM hashes;' ).fetchone()
            
            if result is None:
                
                raise Exception( 'This archive has no hash type set, and as it has no files, no hash type guess can be made.' )
                
            
            if len( hash ) == 16: hash_type = HASH_TYPE_MD5
            elif len( hash ) == 20: hash_type = HASH_TYPE_SHA1
            elif len( hash ) == 32: hash_type = HASH_TYPE_SHA256
            elif len( hash ) == 64: hash_type = HASH_TYPE_SHA512
            else:
                
                raise Exception( 'This archive has non-standard hashes. Something is wrong.' )
                
            
            self.SetHashType( hash_type )
            
            return hash_type
            
        else:
            
            ( hash_type, ) = result
            
            return hash_type
            
        
    
    def GetName( self ):
        
        filename = os.path.basename( self._path )
        
        if '.' in filename:
            
            filename = filename.split( '.', 1 )[0]
            
        
        return filename
        
    
    def GetNumberOfStars( self ) -> int:
        
        result = self._c.execute( 'SELECT number_of_stars FROM number_of_stars;' ).fetchone()
        
        if result is None:
            
            raise Exception( 'This rating archive has no number of stars set.' )
            
        else:
            
            ( number_of_stars, ) = result
            
            return number_of_stars
            
        
    
    def GetRating( self, hash ):
        
        result = self._c.execute( 'SELECT rating FROM ratings WHERE hash = ?;', ( sqlite3.Binary( hash ), ) ).fetchone()
        
        if result is None:
            
            return None
            
        else:
            
            ( rating, ) = result
            
            return rating
            
        
    
    def HasHash( self, hash ) -> bool:
        
        result = self._c.execute( 'SELECT 1 FROM ratings WHERE hash = ?;', ( sqlite3.Binary( hash ), ) ).fetchone()
        
        if result is None:
            
            return False
            
        else:
            
            return True
            
        
    
    def IterateRatings( self ):
        
        for row in self._c.execute( 'SELECT hash, rating FROM ratings;' ):
            
            yield row
            
        
    
    def SetHashType( self, hash_type ) -> None:
        
        self._c.execute( 'DELETE FROM hash_type;' )
        
        self._c.execute( 'INSERT INTO hash_type ( hash_type ) VALUES ( ? );', ( hash_type, ) )
        
    
    def SetNumberOfStars( self, number_of_stars : int ) -> None:
        
        self._c.execute( 'DELETE FROM number_of_stars;' )
        
        self._c.execute( 'INSERT INTO number_of_stars ( number_of_stars ) VALUES ( ? );', ( number_of_stars, ) )
        
    
    def SetRating( self, hash, rating ):
        
        self._c.execute( 'REPLACE INTO ratings ( hash, rating ) VALUES ( ?, ? );', ( sqlite3.Binary( hash ), rating ) )
        
    
