/*
 * Copyright 2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.gradle.api.reporting.model

import org.gradle.integtests.fixtures.AbstractIntegrationSpec

class ModelReportIntegrationTest extends AbstractIntegrationSpec {

    def "displays basic structure of an empty project"() {
        given:
        buildFile

        when:
        run "model"

        then:
        def modelReportOutput = ModelReportOutput.from(output)
        modelReportOutput.hasNodeStructure({
            model() {
                tasks {
                    buildEnvironment()
                    components(nodeValue: "task ':components'", type: 'org.gradle.api.reporting.components.ComponentReport')
                    dependencies()
                    dependencyInsight()
                    help()
                    init()
                    model()
                    projects()
                    properties()
                    tasks()
                    wrapper()
                }
            }
        })
    }

    def "displays collections of scalar types in a human-readable format"() {
        given:
        buildFile << '''

@Managed
interface Container {
   List<String> getLabels()
   List<Integer> getIds()
   List<Double> getValues()
   void setValues(List<Double> values)
}

model {
    container(Container) {
        labels.add 'bug'
        labels.add 'blocker'
    }
}
'''
        when:
        run "model"

        then:
        ModelReportOutput.from(output).hasNodeStructure({
            model {
                container {
                    ids(type: 'java.util.List<java.lang.Integer>', creator: 'container(Container) { ... } @ build.gradle line 12, column 5')
                    labels(type: 'java.util.List<java.lang.String>', creator: 'container(Container) { ... } @ build.gradle line 12, column 5', nodeValue: "[bug, blocker]")
                    values(type: 'java.util.List<java.lang.Double>', creator: 'container(Container) { ... } @ build.gradle line 12, column 5')
                }
                tasks {
                    buildEnvironment(nodeValue: "task ':buildEnvironment'")
                    components(nodeValue: "task ':components'")
                    dependencies(nodeValue: "task ':dependencies'")
                    dependencyInsight(nodeValue: "task ':dependencyInsight'")
                    help(nodeValue: "task ':help'")
                    init(nodeValue: "task ':init'")
                    model(nodeValue: "task ':model'")
                    projects(nodeValue: "task ':projects'")
                    properties(nodeValue: "task ':properties'")
                    tasks(nodeValue: "task ':tasks'")
                    wrapper()
                }
            }
        })
    }

    def "displays basic values of a simple model graph with values"() {
        given:
        buildFile << """

@Managed
public interface PasswordCredentials {
    String getUsername()
    String getPassword()
    void setUsername(String s)
    void setPassword(String s)
}


${managedNumbers()}

model {
    primaryCredentials(PasswordCredentials){
        username = 'uname'
        password = 'hunter2'
    }

    nullCredentials(PasswordCredentials) { }
    numbers(Numbers){
        value = 5
        threshold = 0.8
    }
}

"""
        buildFile
        when:
        run "model"

        then:
        ModelReportOutput.from(output).hasNodeStructure({
            model {
                nullCredentials {
                    password(type: 'java.lang.String', creator: 'nullCredentials(PasswordCredentials) { ... } @ build.gradle line 27, column 5')
                    username(type: 'java.lang.String', creator: 'nullCredentials(PasswordCredentials) { ... } @ build.gradle line 27, column 5')
                }

                numbers {
                    threshold(nodeValue: "0.8")
                    value(nodeValue: "5")
                }
                primaryCredentials {
                    password(nodeValue: 'hunter2', type: 'java.lang.String', creator: 'primaryCredentials(PasswordCredentials) { ... } @ build.gradle line 22, column 5')
                    username(nodeValue: 'uname', type: 'java.lang.String', creator: 'primaryCredentials(PasswordCredentials) { ... } @ build.gradle line 22, column 5')
                }
                tasks {
                    buildEnvironment(nodeValue: "task ':buildEnvironment'")
                    components(nodeValue: "task ':components'")
                    dependencies(nodeValue: "task ':dependencies'")
                    dependencyInsight(nodeValue: "task ':dependencyInsight'")
                    help(nodeValue: "task ':help'")
                    init(nodeValue: "task ':init'")
                    model(nodeValue: "task ':model'")
                    projects(nodeValue: "task ':projects'")
                    properties(nodeValue: "task ':properties'")
                    tasks(nodeValue: "task ':tasks'")
                    wrapper()
                }
            }
        })
    }

    // nb: specifically doesn't use the parsing fixture, so that the output is visualised
    //If you're changing this you will also need to change: src/samples/userguideOutput/basicRuleSourcePlugin-model-task.out
    def "displays a report in the correct format"() {
        given:
        buildFile << """

@Managed
public interface PasswordCredentials {
    String getUsername()
    String getPassword()
    void setUsername(String s)
    void setPassword(String s)
}


${managedNumbers()}

model {
    primaryCredentials(PasswordCredentials){
        username = 'uname'
        password = 'hunter2'
    }

    nullCredentials(PasswordCredentials)
    numbers(Numbers){
        value = 5
        threshold = 0.8
    }
}

"""
        buildFile
        when:
        run "model"

        then:
        def modelReportOutput = ModelReportOutput.from(output)
        modelReportOutput.hasTitle("Root project")

        and:
        modelReportOutput.nodeContentEquals('''
+ nullCredentials
      | Type:   \tPasswordCredentials
      | Creator: \tnullCredentials(PasswordCredentials) @ build.gradle line 27, column 5
    + password
          | Type:   \tjava.lang.String
          | Creator: \tnullCredentials(PasswordCredentials) @ build.gradle line 27, column 5
    + username
          | Type:   \tjava.lang.String
          | Creator: \tnullCredentials(PasswordCredentials) @ build.gradle line 27, column 5
+ numbers
      | Type:   \tNumbers
      | Creator: \tnumbers(Numbers) { ... } @ build.gradle line 28, column 5
    + threshold
          | Type:   \tdouble
          | Value:  \t0.8
          | Creator: \tnumbers(Numbers) { ... } @ build.gradle line 28, column 5
    + value
          | Type:   \tjava.lang.Integer
          | Value:  \t5
          | Creator: \tnumbers(Numbers) { ... } @ build.gradle line 28, column 5
+ primaryCredentials
      | Type:   \tPasswordCredentials
      | Creator: \tprimaryCredentials(PasswordCredentials) { ... } @ build.gradle line 22, column 5
    + password
          | Type:   \tjava.lang.String
          | Value:  \thunter2
          | Creator: \tprimaryCredentials(PasswordCredentials) { ... } @ build.gradle line 22, column 5
    + username
          | Type:   \tjava.lang.String
          | Value:  \tuname
          | Creator: \tprimaryCredentials(PasswordCredentials) { ... } @ build.gradle line 22, column 5
+ tasks
      | Type:   \torg.gradle.model.ModelMap<org.gradle.api.Task>
      | Creator: \tProject.<init>.tasks()
    + buildEnvironment
          | Type:   \torg.gradle.api.tasks.diagnostics.BuildEnvironmentReportTask
          | Value:  \ttask ':buildEnvironment\'
          | Creator: \ttasks.addPlaceholderAction(buildEnvironment)
          | Rules:
             ⤷ copyToTaskContainer
    + components
          | Type:   \torg.gradle.api.reporting.components.ComponentReport
          | Value:  \ttask ':components'
          | Creator: \ttasks.addPlaceholderAction(components)
          | Rules:
             ⤷ copyToTaskContainer
    + dependencies
          | Type:   \torg.gradle.api.tasks.diagnostics.DependencyReportTask
          | Value:  \ttask ':dependencies'
          | Creator: \ttasks.addPlaceholderAction(dependencies)
          | Rules:
             ⤷ copyToTaskContainer
    + dependencyInsight
          | Type:   \torg.gradle.api.tasks.diagnostics.DependencyInsightReportTask
          | Value:  \ttask ':dependencyInsight'
          | Creator: \ttasks.addPlaceholderAction(dependencyInsight)
          | Rules:
             ⤷ HelpTasksPlugin.Rules#addDefaultDependenciesReportConfiguration
             ⤷ copyToTaskContainer
    + help
          | Type:   \torg.gradle.configuration.Help
          | Value:  \ttask ':help'
          | Creator: \ttasks.addPlaceholderAction(help)
          | Rules:
             ⤷ copyToTaskContainer
    + init
          | Type:   \torg.gradle.buildinit.tasks.InitBuild
          | Value:  \ttask ':init'
          | Creator: \ttasks.addPlaceholderAction(init)
          | Rules:
             ⤷ copyToTaskContainer
    + model
          | Type:   \torg.gradle.api.reporting.model.ModelReport
          | Value:  \ttask ':model'
          | Creator: \ttasks.addPlaceholderAction(model)
          | Rules:
             ⤷ copyToTaskContainer
    + projects
          | Type:   \torg.gradle.api.tasks.diagnostics.ProjectReportTask
          | Value:  \ttask ':projects'
          | Creator: \ttasks.addPlaceholderAction(projects)
          | Rules:
             ⤷ copyToTaskContainer
    + properties
          | Type:   \torg.gradle.api.tasks.diagnostics.PropertyReportTask
          | Value:  \ttask ':properties'
          | Creator: \ttasks.addPlaceholderAction(properties)
          | Rules:
             ⤷ copyToTaskContainer
    + tasks
          | Type:   \torg.gradle.api.tasks.diagnostics.TaskReportTask
          | Value:  \ttask ':tasks'
          | Creator: \ttasks.addPlaceholderAction(tasks)
          | Rules:
             ⤷ copyToTaskContainer
    + wrapper
          | Type:   \torg.gradle.api.tasks.wrapper.Wrapper
          | Value:  \ttask ':wrapper'
          | Creator: \ttasks.addPlaceholderAction(wrapper)
          | Rules:
             ⤷ copyToTaskContainer
''')
    }

    def "method rule sources have simple type names and correct order"() {
        given:
        buildFile << """
${managedNumbers()}

class NumberRules extends RuleSource {
    @Model("myNumbers")
    void createRule(Numbers n) {
       n.setValue(5)
       n.setThreshold(0.8)
    }
    @Defaults void defaultsRule(Numbers n) {}
    @Mutate void mutateRule(Numbers n) {}
    @Finalize void finalizeRule(Numbers n) {}
    @Validate void validateRule(Numbers n) {}
}

class ClassHolder {
    static class InnerRules extends RuleSource {
         @Mutate void mutateRule(Numbers n) {}
    }
}

apply plugin: NumberRules
apply plugin: ClassHolder.InnerRules
"""
        buildFile
        when:
        run "model"

        then:
        def modelNode = ModelReportOutput.from(output).modelNode
        modelNode.myNumbers.@creator[0] == 'NumberRules#createRule'

        int i = 0
        def rules = modelNode.myNumbers.@rules[0]
        rules[i++] == 'NumberRules#defaultsRule'
        rules[i++] == 'NumberRules#mutateRule'
        rules[i++] == 'ClassHolder.InnerRules#mutateRule'
        rules[i++] == 'NumberRules#finalizeRule'
        rules[i] == 'NumberRules#validateRule'
    }

    def "service nodes are not displayed on the report"() {
        given:
        buildFile << """
        class Rules extends RuleSource {
            @org.gradle.model.internal.core.Service
            String thingamajigger() {
                return "hello"
            }
        }
        apply plugin: Rules
"""

        when:
        run "model"

        then:
        def modelNode = ModelReportOutput.from(output).modelNode
        !modelNode.thingamajigger
    }

    def "properties on internal views of custom component are hidden in the model report"() {
        given:
        buildFile << """
            interface UnmanagedComponentSpec extends ComponentSpec {}
            class DefaultUnmanagedComponentSpec extends BaseComponentSpec implements UnmanagedComponentSpec {}

            @Managed
            interface SampleComponentSpec extends UnmanagedComponentSpec {
                String getPublicData()
                void setPublicData(String data)
            }

            @Managed
            interface InternalSampleSpec {
                String getPublicData()
                void setPublicData(String data)
                String getInternalData()
                void setInternalData(String data)
            }

            class RegisterComponentRules extends RuleSource {
                @ComponentType
                void register1(ComponentTypeBuilder<UnmanagedComponentSpec> builder) {
                    builder.defaultImplementation(DefaultUnmanagedComponentSpec)
                }

                @ComponentType
                void register2(ComponentTypeBuilder<SampleComponentSpec> builder) {
                    builder.internalView(InternalSampleSpec)
                }
            }
            apply plugin: RegisterComponentRules

            model {
                components {
                    sample(SampleComponentSpec)
                }
            }
        """

        when:
        succeeds "model"

        then:
        def modelNode = ModelReportOutput.from(output).modelNode
        modelNode.components.sample.publicData
        !modelNode.components.sample.internalData

        and:
        succeeds "model", "--showHidden"

        then:
        ModelReportOutput.from(output).modelNode.components.sample.internalData
    }

    def "properties on internal views of custom binaries are hidden in the model report"() {
        given:
        buildFile << """
            interface UnmanagedBinarySpec extends BinarySpec {}
            class DefaultUnmanagedBinarySpec extends BaseBinarySpec implements UnmanagedBinarySpec {}

            @Managed
            interface SampleBinarySpec extends UnmanagedBinarySpec {
                String getPublicData()
                void setPublicData(String data)
            }

            @Managed
            interface InternalSampleSpec {
                String getPublicData()
                void setPublicData(String data)
                String getInternalData()
                void setInternalData(String data)
            }

            class RegisterBinaryRules extends RuleSource {
                @BinaryType
                void register1(BinaryTypeBuilder<UnmanagedBinarySpec> builder) {
                    builder.defaultImplementation(DefaultUnmanagedBinarySpec)
                }

                @BinaryType
                void register2(BinaryTypeBuilder<SampleBinarySpec> builder) {
                    builder.internalView(InternalSampleSpec)
                }
            }
            apply plugin: RegisterBinaryRules

            model {
                binaries {
                    sample(SampleBinarySpec)
                }
            }
        """

        when:
        succeeds "model"

        then:
        def modelNode = ModelReportOutput.from(output).modelNode
        modelNode.binaries.sample.publicData
        !modelNode.binaries.sample.internalData

        and:
        succeeds "model", "--showHidden"

        then:
        ModelReportOutput.from(output).modelNode.binaries.sample.internalData
    }

    private String managedNumbers() {
        return """@Managed
        public interface Numbers {
            Integer getValue()
            void setValue(Integer i)

            double getThreshold()
            void setThreshold(double d)
        }"""
    }
}
