namespace modifies Farm {
    
    abstract class Target {
        
        string uid;
        array  files;
        array  outputs;
        array  properties;
        
        function constructor( string name ) {
            .uid = name;
            .setProperty('name', name);
            .setProperty('version', '1' );
            if( Array.keyExists(Farm.global_properties, 'version') )
                .setProperty( 'version', Farm.global_properties['version'] );            
            if( Array.keyExists(Farm.global_properties, 'prefix') )
                .setProperty( 'prefix', Farm.global_properties['prefix'] );            
        }

        function setType( string t ) {
            .setProperty('type', t);
        }
        function type() {
            return .getProperty('type');
        }
        
        function addFiles( string type, array files ) {
            type = String.toLower(type);
            
            if( not Array.keyExists(.files, type) )
                .files[type] = [];
            
            Array.each( files ) using ( s ) {
                .files[type][] = s;
            };
        }
        
        function invokeBuild() 
            ;
        function invokeInstall()
            ;
        
        function invokeAction( string action ) {
            switch( String.toLower(action) ) {
                case "build":
                    return .invokeBuild();
                case "install":
                    return .invokeInstall();
            }
            return false;
        }
        
        function setProperty( string type, string property ) {
            .properties[type] = property;
        }
        
        function addProperty( string type, string property ) {
            if( Array.keyExists(.properties, type) )
                .properties[type] = String.trim("${.properties[type]} $property"," \n\r\t");
            else
                .properties[type] = String.trim(property," \n\r\t");
        }
        
        function getProperty( string name ) {
            if( Array.keyExists(.properties, name) )
                return .properties[name];
            return "";
        }
        
        function toString() {
            return "Target: ${.getProperty('name')}, Properties: ${.properties}";
        }
    }
    
    class NativeCodeTarget extends Farm.Target {
        
        function addFiles( string type, array sources ) {
            // Add the source files
            super.addFiles( type, sources );
            
            // Scan them for headers
            if( type == 'source' ) {
                object header = new Regexp('\.h$'), match;
                Array.each( sources ) using ( s ) {
                    if( match = header.match(s) ) {
                        array a = String.toArray( s, '/' );
                        if( Array.size(a) > 1 ) // It is not in the current directory
                        {
                            string path = '-I' + (a[0] == "" ? "" : (Directory.getCurrent() + '/')) + Array.join( a[..-2], '/' );
                            string s = "";
                            
                            if( (s = .getProperty('C')) == "" or String.index(s, path) == -1 )
                                .addProperty('C', path);
                            if( (s = .getProperty('CPP')) == "" or String.index(s, path) == -1 )
                                .addProperty('CPP', path);
                        }
                    }
                };
            }
        }        
        
        function sourceListMatching( string match ) {
            array source_list;
            object o = new Regexp( match );
            
            Array.each( .files['source'] ) using ( source ) {
                array list = Farm.RuleSet.translate( self, source );
                Array.each( list ) using ( file ) {
                    if( o.match(file) )
                        source_list[] = file;
                };
            };
            return source_list;
        }
        
        function invokeBuild() {
            string types = Farm.RuleSet.sourceTypes();
            array source_list = .sourceListMatching(types);
            
            .outputs = [];
            return Array.each( source_list ) using ( source_file ) {
                string s = Farm.RuleSet.build( self, source_file );
                if( s != "FAILED" ) {
                    if( s != "" )
                        .outputs[] = s;
                    return true;
                }
                return false;
            };
        }
        function invokeInstall() {
            if( Farm.createPath(.getProperty('prefix')) ) {
                string product = Farm.RuleSet.combinationName(self, .type());
                return Farm.copyFile( product, "${.getProperty('prefix')}/${Farm.basename(product)}", 0644 );
            }
            return false;
        }
    }
    
    class Library extends Farm.NativeCodeTarget {
        function invokeBuild() {
            if( super.invokeBuild() and Array.size( .outputs ) > 0 ) {
                return Farm.RuleSet.combine( self, .outputs, .type() );
            }
            return false;
        }
    }
    class Executable extends Farm.Library {
        static function constructor() {
            Farm.Yard.registerTargetType( 'executable', self );
        }
        
        function constructor( string name ) {
            super( name );
            .setType( Farm.RuleSet.Executable );
        }
    }
    class SharedLibrary extends Farm.Library {
        static function constructor() {
            Farm.Yard.registerTargetType( 'shared-library', self );
        }
        
        function constructor( string name ) {
            super( name );
            .setType( Farm.RuleSet.SharedLibrary );
            .addProperty( 'C', '-fPIC' );
            .addProperty( 'CPP', '-fPIC' );
        }
    }
    class ModuleLibrary extends Farm.Library {
        static function constructor() {
            Farm.Yard.registerTargetType( 'module', self );
        }
        
        function constructor( string name ) {
            super( name );
            .setType( Farm.RuleSet.ModuleLibrary );
            .addProperty( 'C', '-fPIC' );
            .addProperty( 'CPP', '-fPIC' );
        }
    }
    class StaticLibrary extends Farm.Library {
        static function constructor() {
            Farm.Yard.registerTargetType( 'static-library', self );
        }
        
        function constructor( string name ) {
            super( name );
            .setType( Farm.RuleSet.StaticLibrary );
        }
    }
}    