#!/usr/bin/env python3
# -*- coding: iso-8859-1 -*-

import os, sys, struct
from stat import ST_SIZE

PKG_VERSION                = 1

PKG_HEADER_FORMAT          = '3cB' 

PKG_FILE_PATHLENGTH_FORMAT = '!I'
PKG_FILE_DATALENGTH_FORMAT = '!I'

def readHeader( file ):
    'readHeader( file ) -> version'
    
    header = struct.unpack( 
        PKG_HEADER_FORMAT, 
        file.read( struct.calcsize( PKG_HEADER_FORMAT ) ) 
    )
    version = header[3]
    
    size = struct.unpack(
        PKG_FILE_DATALENGTH_FORMAT,
        file.read( struct.calcsize( PKG_FILE_DATALENGTH_FORMAT ) )
    )
    
    return version
    

def writeHeader( file, version ):
    'writeHeader( file, version ) -> None'
    
    file.write( 
        struct.pack( 
            PKG_HEADER_FORMAT, 
            b'P', b'K', b'G', version
        )
    )
    
    file.write(
        struct.pack(
            PKG_FILE_DATALENGTH_FORMAT,
            0
        )
    )
    

def readFile( file ):
    'readFile( file ) -> (path, data)'
      
    pathLength = struct.unpack(
        PKG_FILE_PATHLENGTH_FORMAT,
        file.read( struct.calcsize( PKG_FILE_PATHLENGTH_FORMAT ) )
    )
    
    path = file.read( pathLength[0] )
    file.read(1)
    
    dataLength = struct.unpack(
        PKG_FILE_DATALENGTH_FORMAT,
        file.read( struct.calcsize( PKG_FILE_DATALENGTH_FORMAT ) )
    )
    
    data = file.read( dataLength[0] )
    
    return (path, data)
    

def writeFile( file, path, data ):
    'writeFile( file, path, data ) -> None'
    
    file.write( struct.pack( "!I" + str( ( len( path ) + 4 ) & ~3 ) + "sI", -1 + ( ( len( path ) + 4 ) & ~3 ), bytes(path, "iso-8859-1"), len( data ) ) )
    file.write( data )


def listFiles( path='.' ):
    'listFiles( path ) -> []'
    
    files = []
    for name in os.listdir( path ):
        name = os.path.join( path, name )
        
        if os.path.isdir( name ):
            files.extend( listFiles( name ) )
        else:
            files.append( os.path.normpath( name ) )
            
    return files


def unpack( source, destination ):
    'unpack( source, destination ) -> None'
    
    if not os.path.exists( destination ):
        os.makedirs( destination )
    
    inputSize = os.path.getsize( source )
    input     = open( source, 'rb' )
    
    oldwd = os.getcwd()
    os.chdir( destination )
    
    version = readHeader( input )
    
    while input.tell() < inputSize:
        (path, data) = readFile( input )
        
        print(' %s (%d bytes)' % (path, len( data )))
        
        directory = os.path.dirname( path )
        if directory != '' and not os.path.exists( directory ):
            os.makedirs( directory )
            
        output = open( path, 'wb' )
        output.write( data )
        output.close()
        
    os.chdir( oldwd )
    

def pack( destination, files ):
    'pack( source, destination ) -> None'
    
    output = open( destination, 'wb' )

    oldwd = os.getcwd()
    if os.path.isdir( files[0] ):
        os.chdir( files[0] )
        files = files[1:]
    
    if len(files) == 0:
    	files = listFiles()   

    writeHeader( output, PKG_VERSION )
    
    for path in files:
        print(' %s (%d bytes)' % (path, os.path.getsize( path )))
        
        input = open( path, 'rb' )
        # Record the name the file is known by, never the path it
        # happened to be built at - an absolute path is a property of
        # the build machine, not of the package.
        name = os.path.basename( path ) if os.path.isabs( path ) else path
        writeFile( output, name, input.read() )
        input.close()
    
    os.chdir( oldwd )
    
    output.seek( struct.calcsize( PKG_HEADER_FORMAT ) )
    st = os.fstat( output.fileno() )
    
    output.write(
        struct.pack(
            PKG_FILE_DATALENGTH_FORMAT,
            st[ST_SIZE]
        )
    )
    
    
    output.close()
    
    
if __name__ == '__main__':
    mode        = sys.argv[1]
    
    if   mode == 'c':
        destination = sys.argv[2]
        files = sys.argv[3:]
        pack( destination, files )
    elif mode == 'x':
        source      = sys.argv[2]
        destination = sys.argv[3]
        unpack( source, destination )
    else:
        print('Invalid mode.')

