func main() {
	configs := createConfigsModelFromEnvs()

	fmt.Println()
	configs.print()

	if err := configs.validate(); err != nil {
		log.Error("Issue with input: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	// Get Simulator Infos
	fmt.Println()
	log.Info("Collecting simulator info...")
	simulatorInfo, err := getSimulatorInfo(configs.SimulatorOsVersion, configs.SimulatorDevice)
	if err != nil {
		log.Error("Failed to get simulator infos, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}
	log.Done("Simulator (%s), id: (%s), status: %s", simulatorInfo.Name, simulatorInfo.ID, simulatorInfo.Status)
	// ---

	// Nunit Console path
	nunitConsolePth, err := nunit.SystemNunit3ConsolePath()
	if err != nil {
		log.Error("Failed to get system insatlled nunit3-console.exe path, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}
	// ---

	//
	// build
	fmt.Println()
	log.Info("Building all iOS Xamarin UITest and Referred Projects in solution: %s", configs.XamarinSolution)

	builder, err := builder.New(configs.XamarinSolution, []constants.ProjectType{constants.ProjectTypeIOS}, false)
	if err != nil {
		log.Error("Failed to create xamarin builder, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	callback := func(solutionName string, projectName string, projectType constants.ProjectType, commandStr string, alreadyPerformed bool) {
		fmt.Println()
		if projectType == constants.ProjectTypeXamarinUITest {
			log.Info("Building test project: %s", projectName)
		} else {
			log.Info("Building project: %s", projectName)
		}

		log.Done("$ %s", commandStr)

		if alreadyPerformed {
			log.Warn("build command already performed, skipping...")
		}

		fmt.Println()
	}

	warnings, err := builder.BuildAllXamarinUITestAndReferredProjects(configs.XamarinConfiguration, configs.XamarinPlatform, nil, callback)
	for _, warning := range warnings {
		log.Warn(warning)
	}
	if err != nil {
		log.Error("Build failed, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	projectOutputMap, err := builder.CollectProjectOutputs(configs.XamarinConfiguration, configs.XamarinPlatform)
	if err != nil {
		log.Error("Failed to collect project outputs, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	testProjectOutputMap, warnings, err := builder.CollectXamarinUITestProjectOutputs(configs.XamarinConfiguration, configs.XamarinPlatform)
	for _, warning := range warnings {
		log.Warn(warning)
	}
	if err != nil {
		log.Error("Failed to collect test project output, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}
	// ---

	//
	// Run nunit tests
	nunitConsole, err := nunit.New(nunitConsolePth)
	if err != nil {
		log.Error("Failed to create nunit console model, error: %s", err)

		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
		}

		os.Exit(1)
	}

	resultLogPth := filepath.Join(configs.DeployDir, "TestResult.xml")
	nunitConsole.SetResultLogPth(resultLogPth)

	// Artifacts
	resultLog := ""

	for testProjectName, testProjectOutput := range testProjectOutputMap {
		if len(testProjectOutput.ReferredProjectNames) == 0 {
			log.Warn("Test project (%s) does not refers to any project, skipping...", testProjectName)
			continue
		}

		for _, projectName := range testProjectOutput.ReferredProjectNames {
			projectOutput, ok := projectOutputMap[projectName]
			if !ok {
				continue
			}

			appPth := ""
			for _, output := range projectOutput.Outputs {
				if output.OutputType == constants.OutputTypeAPP {
					appPth = output.Pth
				}
			}

			if appPth == "" {
				log.Error("No app generated for project: %s", projectName)

				if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
					log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
				}

				os.Exit(1)
			}

			// Set APP_BUNDLE_PATH env to let the test know which .app file should be tested
			// This env is used in the Xamarin.UITest project to refer to the .app path
			if err := os.Setenv("APP_BUNDLE_PATH", appPth); err != nil {
				log.Error("Failed to set APP_BUNDLE_PATH environment, without this env test will fail, error: %s", err)

				if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
					log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
				}

				os.Exit(1)
			}

			// Run test
			fmt.Println()
			log.Info("Testing (%s) against (%s)", testProjectName, projectName)
			log.Detail("test dll: %s", testProjectOutput.Output.Pth)
			log.Detail("app: %s", appPth)

			nunitConsole.SetDLLPth(testProjectOutput.Output.Pth)
			nunitConsole.SetTestToRun(configs.TestToRun)

			fmt.Println()
			log.Info("Running Xamarin UITest")
			log.Done("$ %s", nunitConsole.PrintableCommand())
			fmt.Println()

			err := nunitConsole.Run()
			testLog, readErr := testResultLogContent(resultLogPth)
			if readErr != nil {
				log.Warn("Failed to read test result, error: %s", readErr)
			}
			resultLog = testLog

			if err != nil {
				log.Error("Test failed, error: %s", err)

				if errorMsg, err := parseErrorFromResultLog(resultLog); err != nil {
					log.Warn("Failed to parse error message from result log, error: %s", err)
				} else if errorMsg != "" {
					log.Error("%s", errorMsg)
				}

				if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "failed"); err != nil {
					log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
				}

				if resultLog != "" {
					if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", resultLog); err != nil {
						log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", err)
					}
				}

				os.Exit(1)
			}
		}
	}

	if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_RESULT", "succeeded"); err != nil {
		log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_RESULT", err)
	}

	if resultLog != "" {
		if err := exportEnvironmentWithEnvman("BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", resultLog); err != nil {
			log.Warn("Failed to export environment: %s, error: %s", "BITRISE_XAMARIN_TEST_FULL_RESULTS_TEXT", err)
		}
	}
}
// BuildAllNunitTestProjects ...
func (builder Model) BuildAllNunitTestProjects(configuration, platform string, prepareCallback PrepareCommandCallback, callback BuildCommandCallback) ([]string, error) {
	warnings := []string{}

	if err := validateSolutionConfig(builder.solution, configuration, platform); err != nil {
		return warnings, err
	}

	buildableProjects, warns := builder.buildableNunitTestProjects(configuration, platform)
	if len(buildableProjects) == 0 {
		return warns, fmt.Errorf("No project to build found")
	}

	nunitConsolePth, err := nunit.SystemNunit3ConsolePath()
	if err != nil {
		return warnings, err
	}

	perfomedCommands := []tools.Printable{}

	//
	// First build solution
	buildCommand, err := builder.buildSolutionCommand(configuration, platform)
	if err != nil {
		return warnings, fmt.Errorf("Failed to create build command, error: %s", err)
	}

	// Callback to let the caller to modify the command
	if prepareCallback != nil {
		editabeCommand := tools.Editable(buildCommand)
		prepareCallback(builder.solution.Name, "", constants.ProjectTypeUnknown, &editabeCommand)
	}

	// Check if same command was already performed
	alreadyPerformed := false
	if tools.PrintableSliceContains(perfomedCommands, buildCommand) {
		alreadyPerformed = true
	}

	// Callback to notify the caller about next running command
	if callback != nil {
		callback(builder.solution.Name, "", constants.ProjectTypeUnknown, buildCommand.PrintableCommand(), alreadyPerformed)
	}

	if !alreadyPerformed {
		if err := buildCommand.Run(); err != nil {
			return warnings, err
		}
		perfomedCommands = append(perfomedCommands, buildCommand)
	}
	// ---

	//
	// Then build all test projects
	for _, testProj := range buildableProjects {
		buildCommand, warns, err := builder.buildNunitTestProjectCommand(configuration, platform, testProj, nunitConsolePth)
		warnings = append(warnings, warns...)
		if err != nil {
			return warnings, fmt.Errorf("Failed to create build command, error: %s", err)
		}

		// Callback to let the caller to modify the command
		if prepareCallback != nil {
			editabeCommand := tools.Editable(buildCommand)
			prepareCallback(builder.solution.Name, testProj.Name, testProj.ProjectType, &editabeCommand)
		}

		// Check if same command was already performed
		alreadyPerformed := false
		if tools.PrintableSliceContains(perfomedCommands, buildCommand) {
			alreadyPerformed = true
		}

		// Callback to notify the caller about next running command
		if callback != nil {
			callback(builder.solution.Name, testProj.Name, testProj.ProjectType, buildCommand.PrintableCommand(), alreadyPerformed)
		}

		if !alreadyPerformed {
			if err := buildCommand.Run(); err != nil {
				return warnings, err
			}
			perfomedCommands = append(perfomedCommands, buildCommand)
		}
	}
	// ---

	return warnings, nil
}