module Fastlane
  module Actions
    module SharedValues
      XCODEBUILD_ARCHIVE = :XCODEBUILD_ARCHIVE
    end

    # xcodebuild man page:
    # https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/xcodebuild.1.html

    class XcodebuildAction
      ARGS_MAP = {
        # actions
        analyze: "analyze",
        archive: "archive",
        build: "build",
        clean: "clean",
        install: "install",
        installsrc: "installsrc",
        test: "test",

        # parameters
        alltargets: "-alltargets",
        arch: "-arch",
        archive_path: "-archivePath",
        configuration: "-configuration",
        derivedDataPath: "-derivedDataPath",
        destination: "-destination",
        export_archive: "-exportArchive",
        export_format: "-exportFormat",
        export_installer_identity: "-exportInstallerIdentity",
        export_path: "-exportPath",
        export_profile: "-exportProvisioningProfile",
        export_signing_identity: "-exportSigningIdentity",
        export_with_original_signing_identity: "-exportWithOriginalSigningIdentity",
        project: "-project",
        result_bundle_path: "-resultBundlePath",
        scheme: "-scheme",
        sdk: "-sdk",
        skip_unavailable_actions: "-skipUnavailableActions",
        target: "-target",
        workspace: "-workspace",
        xcconfig: "-xcconfig"
      }

      def self.run(params)
        unless Helper.test?
          raise "xcodebuild not installed".red if `which xcodebuild`.length == 0
        end

        # The args we will build with
        cli_args = Array[]

        # Supported ENV vars
        build_path = ENV["BUILD_PATH"] || ""
        scheme     = ENV["SCHEME"]
        workspace  = ENV["WORKSPACE"]
        project    = ENV["PROJECT"]

        # Append slash to build path, if needed
        unless build_path.end_with? "/"
          build_path += "/"
        end

        if params = params.first
          # Operation bools
          archiving = params.key? :archive
          exporting = params.key? :export_archive
          testing   = params.key? :test

          if exporting
            # If not passed, retrieve path from previous xcodebuild call
            params[:archive_path] ||= Actions.lane_context[SharedValues::XCODEBUILD_ARCHIVE]

            # Default to ipa as export format
            params[:export_format] ||= "ipa"

            # If not passed, construct export path from env vars
            params[:export_path] ||= "#{build_path}#{scheme}"

            # Store IPA path for later deploy steps (i.e. Crashlytics)
            Actions.lane_context[SharedValues::IPA_OUTPUT_PATH] = params[:export_path] + "." + params[:export_format].downcase
          else
            # If not passed, check for archive scheme & workspace/project env vars
            params[:scheme] ||= scheme
            params[:workspace] ||= workspace
            params[:project] ||= project
          end

          if archiving
            # If not passed, construct archive path from env vars
            params[:archive_path] ||= "#{build_path}#{params[:scheme]}.xcarchive"

            # Cache path for later xcodebuild calls
            Actions.lane_context[SharedValues::XCODEBUILD_ARCHIVE] = params[:archive_path]
          end

          # Maps parameter hash to CLI args
          if hash_args = hash_to_cli_args(params)
            cli_args += hash_args
          end
        end

        # Joins args into space delimited string
        cli_args = cli_args.join(" ")

        output_format = testing && !archiving ? "--test" : "--simple"

        Actions.sh "xcodebuild #{cli_args} | xcpretty #{output_format} --color"
      end

      def self.hash_to_cli_args(hash)
        # Remove nil value params
        hash = hash.delete_if { |_, v| v.nil? }

        # Maps nice developer param names to CLI arguments
        hash.map do |k, v|
          v ||= ""
          if arg = ARGS_MAP[k]
            value = (v != true && v.to_s.length > 0 ? "\"#{v}\"" : "")
            "#{arg} #{value}".strip
          elsif k == :keychain && v.to_s.length > 0
            # If keychain is specified, append as OTHER_CODE_SIGN_FLAGS
            "OTHER_CODE_SIGN_FLAGS=\"--keychain #{v}\""
          end
        end.compact.sort
      end
    end

    class XcarchiveAction
      def self.run(params)
        params_hash = params.first || {}
        params_hash[:archive] = true
        XcodebuildAction.run([params_hash])
      end
    end

    class XcbuildAction
      def self.run(params)
        params_hash = params.first || {}
        params_hash[:build] = true
        XcodebuildAction.run([params_hash])
      end
    end

    class XccleanAction
      def self.run(params)
        params_hash = params.first || {}
        params_hash[:clean] = true
        XcodebuildAction.run([params_hash])
      end
    end

    class XcexportAction
      def self.run(params)
        params_hash = params.first || {}
        params_hash[:export_archive] = true
        XcodebuildAction.run([params_hash])
      end
    end

    class XctestAction
      def self.run(params)
        params_hash = params.first || {}
        params_hash[:test] = true
        XcodebuildAction.run([params_hash])
      end
    end
  end
end