/**
    Example groups are logical groupings of examples, defined with
    the `describe` and `context` functions. Example groups can share
    setup and teardown code.
*/
@objc final public class ExampleGroup {
    weak internal var parent: ExampleGroup?
    internal let hooks = ExampleHooks()

    private let description: String
    private let flags: FilterFlags
    private let isInternalRootExampleGroup: Bool
    private var childGroups = [ExampleGroup]()
    private var childExamples = [Example]()

    internal init(description: String, flags: FilterFlags, isInternalRootExampleGroup: Bool = false) {
        self.description = description
        self.flags = flags
        self.isInternalRootExampleGroup = isInternalRootExampleGroup
    }

    /**
        Returns a list of examples that belong to this example group,
        or to any of its descendant example groups.
    */
    public var examples: [Example] {
        var examples = childExamples
        for group in childGroups {
            examples.extend(group.examples)
        }
        return examples
    }

    internal var name: String? {
        if let parent = parent {
            switch(parent.name) {
            case .Some(let name): return "\(name), \(description)"
            case .None: return description
            }
        } else {
            return isInternalRootExampleGroup ? nil : description
        }
    }

    internal var filterFlags: FilterFlags {
        var aggregateFlags = flags
        walkUp() { (group: ExampleGroup) -> () in
            for (key, value) in group.flags {
                aggregateFlags[key] = value
            }
        }
        return aggregateFlags
    }

    internal var befores: [BeforeExampleWithMetadataClosure] {
        var closures = hooks.befores.reverse()
        walkUp() { (group: ExampleGroup) -> () in
            closures.extend(group.hooks.befores.reverse())
        }
        return closures.reverse()
    }

    internal var afters: [AfterExampleWithMetadataClosure] {
        var closures = hooks.afters
        walkUp() { (group: ExampleGroup) -> () in
            closures.extend(group.hooks.afters)
        }
        return closures
    }

    internal func walkDownExamples(callback: (example: Example) -> ()) {
        for example in childExamples {
            callback(example: example)
        }
        for group in childGroups {
            group.walkDownExamples(callback)
        }
    }

    internal func appendExampleGroup(group: ExampleGroup) {
        group.parent = self
        childGroups.append(group)
    }

    internal func appendExample(example: Example) {
        example.group = self
        childExamples.append(example)
    }

    private func walkUp(callback: (group: ExampleGroup) -> ()) {
        var group = self
        while let parent = group.parent {
            callback(group: parent)
            group = parent
        }
    }
}