# Copyright 2004-2008 Roman Yakovenko.
# Distributed under the Boost Software License, Version 1.0. (See
# accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)

"""
This module is a collection of unrelated algorithms, that works on code creators
tree.
"""
import os
import math
from pygccxml import declarations
from pyplusplus import code_creators  

class missing_call_policies:
    @staticmethod
    def _selector( creator ):
        if not isinstance( creator, code_creators.declaration_based_t ):
            return False
        if not isinstance( creator.declaration, declarations.calldef_t ):
            return False
        if isinstance( creator.declaration, declarations.constructor_t ):
            return False
        return hasattr(creator, 'call_policies') and not creator.call_policies

    @staticmethod    
    def print_( extmodule ):
        creators = filter( missing_call_policies._selector
                           , code_creators.make_flatten_generator( extmodule.creators ) )
        for creator in creators:
            print creator.declaration.__class__.__name__, ': ', declarations.full_name( creator.declaration )
            print '  *** MISSING CALL POLICY', creator.declaration.function_type().decl_string
            print 

    @staticmethod    
    def exclude( extmodule ):
        creators = filter( missing_call_policies._selector
                           , code_creators.make_flatten_generator( extmodule.creators ) )
        for creator in creators:
            creator.parent.remove_creator( creator )
    
def split_sequence(seq, bucket_size):
    #split sequence to buckets, where every will contain maximum bucket_size items
    seq_len = len( seq )
    if seq_len <= bucket_size:
        return [ seq ] 
    buckets = []
    num_of_buckets = int( math.ceil( float( seq_len ) / bucket_size ) )
    for i in range(num_of_buckets):
        from_ = i * bucket_size
        to = min( ( i + 1) * bucket_size, seq_len )
        buckets.append( seq[ from_ : to ] )
    return buckets
    

class exposed_decls_db_t( object ):
    DEFAULT_FILE_NAME = 'exposed_decl.pypp.txt'
    class row_t( declarations.decl_visitor_t ):
        FIELD_DELIMITER = '@'                
        EXPOSED_DECL_SIGN = '+'
        UNEXPOSED_DECL_SIGN = '~'
        CALLDEF_SIGNATURE_DELIMITER = '#'
                
        def __init__( self, decl_or_string ):
            self.key = ''
            self.signature = ''
            self.exposed_sign = ''
            self.normalized_name = ''                        
            if isinstance( decl_or_string, declarations.declaration_t ):
                self.__init_from_decl( decl_or_string )
            else:
                self.__init_from_str( decl_or_string )

        def find_out_normalized_name( self, decl ):
            if decl.name: 
                return decl.partial_name
            elif decl.location:#unnamed enums, classes, unions
                return str( decl.location.as_tuple() )
            elif isinstance( decl, declarations.namespace_t ):
                return '' #I don't really care about unnamed namespaces
            else: #this should nevere happen
                raise RuntimeError( "Unable to create normalized name for declaration: " + str(decl))

        def __init_from_str( self, row ):
            self.exposed_sign, self.key, self.normalized_name, self.signature \
                = row.split( self.FIELD_DELIMITER )

        def update_key( self, cls ):
            self.key = cls.__name__
            
        def __init_from_decl( self, decl ):
            if decl.ignore:
                self.exposed_sign = self.UNEXPOSED_DECL_SIGN
            else:
                self.exposed_sign = self.EXPOSED_DECL_SIGN
            
            self.update_key( decl.__class__ )
            
            self.signature = decl.create_decl_string( with_defaults=False )
            if isinstance( decl, declarations.calldef_t ):
                self.signature = self.signature + decl.function_type().decl_string
                
            self.normalized_name = self.find_out_normalized_name( decl )
            
        def __str__( self ):
            return self.FIELD_DELIMITER.join([ self.exposed_sign
                                               , self.key
                                               , self.normalized_name
                                               , self.signature])
        
        def does_refer_same_decl( self, other ):
            return self.key == other.key \
                   and self.signature == other.signature \
                   and self.normalized_name == other.normalized_name
        
    def __init__( self ):
        self.__registry = {} # key : { name : set(row) }
        self.__row_delimiter = os.linesep
        
    def save( self, fpath ):
        if os.path.isdir( fpath ):
            fpath = os.path.join( fpath, self.DEFAULT_FILE_NAME )
        f = file( fpath, 'w+b' )
        for name2rows in self.__registry.itervalues():
            for rows in name2rows.itervalues():
                for row in rows:
                    f.write( '%s%s' % ( str(row), self.__row_delimiter ) )
        f.close()

    def load( self, fpath ):
        if os.path.isdir( fpath ):
            fpath = os.path.join( fpath, self.DEFAULT_FILE_NAME )        
        f = file( fpath, 'r+b' )
        for line in f:
            row = self.row_t( line.replace( self.__row_delimiter, '' ) )            
            self.__update_registry( row )
    
    def __update_registry( self, row ):        
        if not self.__registry.has_key( row.key ):            
            self.__registry[ row.key ] = { row.normalized_name : [row] }
        else:       
            if not self.__registry[ row.key ].has_key( row.normalized_name ):
                self.__registry[ row.key ][row.normalized_name] = [row]
            else:
                self.__registry[ row.key ][row.normalized_name].append(row)
    
    def __find_row_in_registry( self, row ):
        try:
            decls = filter( lambda rrow: rrow.does_refer_same_decl( row )
                            , self.__registry[ row.key ][ row.normalized_name ] )
            if decls:
                return decls[0]
            else:
                return None
        except KeyError:
            return None
        
    def __find_in_registry( self, decl ):
        row = self.row_t( decl )
        found = self.__find_row_in_registry( row )
        if found:
            return found        
        if isinstance( decl, declarations.class_t ):
            row.update_key( declarations.class_declaration_t )
            found = self.__find_row_in_registry( row )
            if found:
                return found        
        if isinstance( decl, declarations.class_declaration_t ):
            row.update_key( declarations.class_t )
            found = self.__find_row_in_registry( row )
            if found:
                return found        
        return None
        
    def is_exposed( self, decl ):
        row = self.__find_in_registry( decl)
        return row and self.row_t.EXPOSED_DECL_SIGN == row.exposed_sign
        
    def update_decls( self, global_ns ):
        for decl in global_ns.decls():
            row = self.__find_in_registry( decl )
            if not row:
                continue
            if self.row_t.EXPOSED_DECL_SIGN == row.exposed_sign:
                decl.ignore = False
                decl.already_exposed = True
            else:
                decl.ignore = True
                decl.already_exposed = False

    def register_decls( self, global_ns, special_decls ):
        """register decls in the database
        
        global_ns - reference to the global namespace object 
        special_decls - set of declarations, which were exposed, even so they 
        were not ``included``. For example std containers.
        """        
        for decl in global_ns.decls():
            row = self.row_t( decl )
            if decl in special_decls:
                row.exposed_sign = row.EXPOSED_DECL_SIGN    
            self.__update_registry( row )
