//
//  File.swift
//  
//
//  Created by Yonas Kolb on 1/5/20.
//

import Foundation
import XcodeProj
import JSONUtilities

public enum BuildPhaseSpec: Equatable {
    case sources
    case headers
    case resources
    case copyFiles(CopyFilesSettings)
    case none
    // Not currently exposed as selectable options, but used internally
    case frameworks
    case runScript
    case carbonResources

    public struct CopyFilesSettings: Equatable, Hashable {
        public static let xpcServices = CopyFilesSettings(
            destination: .productsDirectory,
            subpath: "$(CONTENTS_FOLDER_PATH)/XPCServices",
            phaseOrder: .postCompile
        )

        public static let plugins = CopyFilesSettings(
            destination: .plugins,
            subpath: "$(CONTENTS_FOLDER_PATH)/PlugIns",
            phaseOrder: .postCompile
        )

        public enum Destination: String {
            case absolutePath
            case productsDirectory
            case wrapper
            case executables
            case resources
            case javaResources
            case frameworks
            case sharedFrameworks
            case sharedSupport
            case plugins

            public var destination: PBXCopyFilesBuildPhase.SubFolder? {
                switch self {
                case .absolutePath: return .absolutePath
                case .productsDirectory: return .productsDirectory
                case .wrapper: return .wrapper
                case .executables: return .executables
                case .resources: return .resources
                case .javaResources: return .javaResources
                case .frameworks: return .frameworks
                case .sharedFrameworks: return .sharedFrameworks
                case .sharedSupport: return .sharedSupport
                case .plugins: return .plugins
                }
            }
        }

        public enum PhaseOrder: String {
            /// Run before the Compile Sources phase
            case preCompile
            /// Run after the Compile Sources and post-compile Run Script phases
            case postCompile
        }

        public var destination: Destination
        public var subpath: String
        public var phaseOrder: PhaseOrder

        public init(
            destination: Destination,
            subpath: String,
            phaseOrder: PhaseOrder
        ) {
            self.destination = destination
            self.subpath = subpath
            self.phaseOrder = phaseOrder
        }
    }

    public var buildPhase: BuildPhase? {
        switch self {
        case .sources: return .sources
        case .headers: return .headers
        case .resources: return .resources
        case .copyFiles: return .copyFiles
        case .frameworks: return .frameworks
        case .runScript: return .runScript
        case .carbonResources: return .carbonResources
        case .none: return nil
        }
    }
}

extension BuildPhaseSpec {

    public init(string: String) throws {
        switch string {
        case "sources": self = .sources
        case "headers": self = .headers
        case "resources": self = .resources
        case "copyFiles":
            throw SpecParsingError.invalidSourceBuildPhase("copyFiles must specify a \"destination\" and optional \"subpath\"")
        case "none": self = .none
        default:
            throw SpecParsingError.invalidSourceBuildPhase(string.quoted)
        }
    }
}

extension BuildPhaseSpec: JSONObjectConvertible {

    public init(jsonDictionary: JSONDictionary) throws {
        self = .copyFiles(try jsonDictionary.json(atKeyPath: "copyFiles"))
    }
}

extension BuildPhaseSpec: JSONEncodable {
    public func toJSONValue() -> Any {
        switch self {
        case .sources: return "sources"
        case .headers: return "headers"
        case .resources: return "resources"
        case .copyFiles(let files): return ["copyFiles": files.toJSONValue()]
        case .none: return "none"
        case .frameworks: fatalError("invalid build phase")
        case .runScript: fatalError("invalid build phase")
        case .carbonResources: fatalError("invalid build phase")
        }
    }
}

extension BuildPhaseSpec.CopyFilesSettings: JSONObjectConvertible {

    public init(jsonDictionary: JSONDictionary) throws {
        destination = try jsonDictionary.json(atKeyPath: "destination")
        subpath = jsonDictionary.json(atKeyPath: "subpath") ?? ""
        phaseOrder = .postCompile
    }
}

extension BuildPhaseSpec.CopyFilesSettings: JSONEncodable {
    public func toJSONValue() -> Any {
        [
            "destination": destination.rawValue,
            "subpath": subpath,
        ]
    }
}
