"""Link all files in a distribution from relative location on the hard disk"""
import os, logging, shutil
from optparse import OptionParser

log = logging.getLogger( __name__ )

def link_tree( distribution_dir, target_dir='/', symbolic=True ):
    """(Sym)link all files under distribution_dir from target_dir 
    """
    if symbolic:
        link = os.symlink 
    else:
        link = os.link
    for path, subdirs, filenames in os.walk( distribution_dir ):
        dir_relative = os.path.relpath( path, distribution_dir )
        dir_source = os.path.normpath( os.path.abspath( path ) )
        dir_final = os.path.normpath( os.path.join( target_dir, dir_relative ))
        if not os.path.exists( dir_final ):
            log.info( 'Creating directory: %s', dir_final )
            try:
                os.makedirs( dir_final )
            except Exception as err:
                err.args += (dir_final,)
                raise 
        for filename in filenames + subdirs:
            file_source = os.path.join( dir_source, filename )
            file_final = os.path.join( dir_final, filename )
            if os.path.islink( file_source ):
                
                target = os.readlink( file_source )
                final_target = os.path.join( dir_final,target)
                log.info('Recreating link in %s to %s',file_final, final_target )
                create_link( final_target, file_final, os.symlink )
            elif os.path.isfile(file_source):
                if os.path.lexists( file_final ):
                    if (
                        symbolic and 
                        os.path.islink( file_final ) and 
                        os.readlink( file_final ) == file_source
                    ):
                        log.info( 'Up to date: %s', file_final )
                        continue
                    log.info( 'Replacing: %s', file_final )
                    create_link( file_source, file_final, link )
                else:
                    log.info( 'Linking: %s', file_final )
                    create_link( file_source, file_final, link )

def create_link( file_source, file_final, link ):
    try:
        temp = file_final + '~'
        if os.path.exists( temp ) or os.path.lexists( temp ):
            try:
                os.remove( temp )
            except Exception as err:
                err.args += (temp, )
                raise 
        try:
            link( file_source, temp )
        except OSError as err:
            if link is os.link:
                shutil.copy2( file_source, temp )
            else:
                raise
        os.rename( temp, file_final )
    except Exception as err:
        log.error( "Error linking %s: %s", file_final, err )
        raise err
    
def get_options():
    """Creates the OptionParser used in :func:`main` """
    parser = OptionParser()
    parser.add_option(
        '-d','--directory',
        dest = 'directory',
        default = None,
        help="Directory to which to link",
    )
    parser.add_option(
        '-t','--target',
        dest = 'target',
        default = None,
        help="Directory in which to create the links",
    )
    parser.add_option(
        '-v','--verbose',
        dest = 'verbose',
        action="store_true",
        default = False,
        help="Perform verbose logging (INFO level), default: False",
    )
    parser.add_option(
        '-H','--hard',
        dest = 'hardlink',
        action="store_true",
        default = False,
        help="Use hard-links instead of symlinks, default: False",
    )
    return parser

def main():
    options,args = get_options().parse_args()
    if options.verbose:
        logging.basicConfig( level=logging.INFO )
    else:
        logging.basicConfig( level=logging.WARNING )
    if args:
        if not options.directory:
            options.directory = args[0]
            args = args[1:]
        if args and not options.target:
            options.target = args[0]
            args = args[1:]
    link_tree( options.directory, options.target, symbolic = (not options.hardlink) )
    return 0
