namespace modifies Farm {
    
    class Yard extends XML.TreeParser {
        
        static array phaseActions;
        static function registerAction( string name, void klass ) {
            .phaseActions[String.toLower(name)] = klass;
        }
        
        static function walkPhaseAction( object element ) {

            object action = null;
            string name = String.toLower(element.getElementName());
            
            if( Array.keyExists( .phaseActions, name ) ) {
                void klass = .phaseActions[name];
                action = new klass( element );
            } else if( name != "text" ) {
                Farm.displayAction('warning', "Unsupported action type: ${element.getElementName()}");                    
            }        
            return action;
        }

        static function walkFileDescripton( object element ) {
            array filelist = [];
            array attributes = element.getAttributes();
            
            switch( element.getElementName() ) {
                case 'file':                    
                    filelist[] = Farm.expandStringProperties( attributes['name'] );
                    break;
                case 'fileset':
                {
                    string directory = String.postTrim( attributes['dir'], '/' );
                    object r = new Regexp(attributes['match']);
                    if( r != null ) {
                        Directory.open( Farm.expandStringProperties(directory) ) using ( file ) {
                            if( r.match(file) ) {
                                filelist[] = "$directory/$file";
                            }
                        };
                    }
                    break;
                }
                case "text": // Ignore text
                case "comment":
                    break;
                default:
                    Farm.displayAction('warning', "Unsupported file description: ${element.getElementName()}");                    
            }
            return filelist;            
        }
        
        static function walkStringValue( object element ) {
            array attributes = element.getAttributes();
            string str = "";
            
            switch( element.getElementName() ) {    
                case 'line':
                {
                    Array.each( element.getChildren() ) using ( child ) {
                        str += .walkStringValue(child);
                    };
                    break;
                }                    
                case 'text':
                    str = Farm.expandStringProperties(element.getElementData());
                    break;
                case 'program-output':
                {
                    array programs = String.toArray(attributes['program'], ',');
                    Array.each( programs ) using ( program ) {
                        string path = Farm.locateBinary( program, Farm.searchPaths() );
                        if( path != "" ) {
                            str = Farm.programOutput( program, Farm.expandStringProperties(attributes['arguments']) );
                            return false;
                        }
                    };
                    break;
                }
                default:
                    Farm.displayAction('warning', "Unsupported property value type: ${element.getElementName()}");                    
            }
            return String.trim(str, "\n\t\r ");
        }
        
        static function walkTargetDirectives( object target, object element ) {
            array attributes = element.getAttributes();
            string name = element.getElementName();
            switch( name ) {    
                case 'text':
                case "comment":
                    break;
                case 'list':
                {
                    array filelist = [];
                    Array.each( element.getChildren() ) using ( child ) {
                        array a = Farm.Yard.walkFileDescripton( child );
                        Array.each( a ) using ( file ) {
                            filelist[] = file;
                        };
                    };
                    target.addFiles( attributes['type'], filelist );
                    break;
                }                     
                case 'property':
                case 'set-property':
                case 'add-property':
                {
                    string flags = "";
                    string type = "";
                    string value = "";
                    string platform = "";
                    
                    if( not Array.keyExists(attributes, 'platform') or attributes['platform'] == Farm.platform )
                    {
                        if( not Array.keyExists(attributes,'type') ) {
                            
                            Farm.displayAction('error', "No type specified for for property '${attributes['id']}'" );
                            return false;
                            
                        } else {
                            
                            type = "${attributes['type']}";
                            
                            // If we are generating flags
                            if( Array.keyExists(attributes,'value') ) {
                                // We have a straight laced value
                                value = Farm.expandStringProperties(attributes['value']);
                                if( element.hasChildren() ) {
                                    Farm.displayAction('warning', "Children of property ${attributes['id']} ignored");
                                }
                            } else {
                                // We have a set of children
                                array children = element.getChildren();                                
                                Array.each( children ) using ( child ) {
                                    string v = Farm.Yard.walkStringValue( child );
                                    if( v !=  "" )
                                        value += (value == "" ? "" : " ") + v;
                                };
                            }
                            
                            if( value == "" ) {
                                Farm.displayAction('warning', "No Property value for $type");
                                return false;
                            }
                            
                            if( name == 'add-property' )
                                target.addProperty( type, value );                            
                            else
                                target.setProperty( type, value );
                        }
                    }
                    break;
                }
                default:
                    Farm.displayAction('warning', "Unsupported target directive type: ${element.getElementName()}");
                    return false;
            }
            return true;
        }

        static array targetTypes;
        static function registerTargetType( string name, void klass ) {
            .targetTypes[String.toLower(name)] = klass;
        }
        
        function walk( object element, object target ) {
            if( element != null ) {                
                array attributes = element.getAttributes();
                
                switch( element.getElementName() ) {
                    case 'yard':
                        .walk( element.getFirstChild(), null );
                        break;
                    case 'include':
                    {
                        include( "${Farm.toplevelDirectory}/${attributes['file']}" );
                        break;
                    }                        
                    case 'property':
                    {
                        string flags = "";
                        string type = "";
                        string value = "";
                        
                        if( not Array.keyExists(attributes, 'id') ) {
                            
                            Farm.displayAction('error', 'A property tag *must* contain an id attribute outside of a target');
                            Sys.exit(1);
                            
                        } else {
                            // If we are generating flags
                            if( Array.keyExists(attributes,'value') ) {
                                // We have a straight laced value
                                value = Farm.expandStringProperties(attributes['value']);
                                if( element.hasChildren() ) {
                                    Farm.displayAction('warning', "Children of property ${attributes['id']} ignored");
                                }
                            } else {
                                // We have a set of children
                                array children = element.getChildren();                                
                                Array.each( children ) using ( child ) {
                                    string v = Farm.Yard.walkStringValue( child );
                                    if( v !=  "" )
                                        value += (value == "" ? "" : " ") + v;
                                };
                            }
                            
                            Farm.global_properties[attributes['id']] = value;
                        }
                        break;
                    }
                    case 'phase':
                    {
                        object phase = new Farm.Phase(attributes['id']);
                        Array.each( element.getChildren() ) using ( child ) {
                            object o = Farm.Yard.walkPhaseAction( child );
                            if( o != null )
                                phase.addAction( o );
                        }; 
                        Farm.phases[phase.id] = phase;
                        if( Array.keyExists(attributes,'depends') )
                            phase.dependancy = attributes['depends'];
                        break;
                    }
                    case 'text':
                    case 'comment':
                        break;
                    default:
                    {
                        string name = element.getElementName();
                        if( target == null ) {
                            if( Array.keyExists(Farm.Yard.targetTypes,name) ) {
                                void klass = Farm.Yard.targetTypes[name];
                                object t = new klass(attributes['id']);
                                if( t != null ) {
                                    if( not Array.each( element.getChildren() ) using ( child ) {
                                        return Farm.Yard.walkTargetDirectives( t, child );
                                    } ) {
                                        Farm.displayAction('error',"Unable to create target '$name:${attributes['id']}'");
                                        Sys.exit(1);
                                    } else {
                                        Farm.addTarget( t.uid, t );
                                    }
                                } else {
                                    Farm.displayAction('error',"Unable to create target '$name:${attributes['id']}'");
                                    Sys.exit(1);
                                }
                            } else {
                                Farm.displayAction('error',"Unknown target type '${element.getElementName()}'");
                                Sys.exit(1);
                            }                            
                        } else {
                            Farm.displayAction('error',"Unknown target directive tag '${element.getElementName()}'");
                            Sys.exit(1);
                        }
                    }
                }
                
                .walk( element.getNextSibling(), target );
            }
        }
        
        function loadYard( string file ) {
            .parseFile( file );
            .walk( .getDocumentElement(), null );
        }        
    }
}