
struct VideoBlock: OrderedKeyBlockProcessor {

    static let blockId: ContentBlock = .video

    let content: Content

    let results: PageGenerationResults

    let language: ContentLanguage

    init(content: Content, results: PageGenerationResults, language: ContentLanguage) {
        self.content = content
        self.results = results
        self.language = language
    }

    func process(_ arguments: [(key: Key, value: String)], markdown: Substring) -> String {
        var options: [Option] = []
        var sources: [Source] = []

        for (key, value) in arguments {
            guard let sourceType = key.sourceType else {
                guard let option = makeOption(key: key, value: value) else {
                    invalid(markdown)
                    continue
                }
                options.append(option)
                continue
            }
            let fileId = value.removingSurroundingQuotes
            guard let file = content.file(fileId) else {
                results.missing(file: fileId, source: "Video Block: \(key)")
                continue
            }
            guard file.type.isVideo else {
                invalid(markdown)
                continue
            }
            results.require(file: file)
            let source = Source(file: file, type: sourceType)
            sources.append(source)
        }

        guard !sources.isEmpty else {
            invalid(markdown)
            return ""
        }

        return VersionedVideo(sources: sources, options: options).content
    }

    private func makeOption(key: Key, value: String) -> Option? {
        switch key {
        case .controls: return .controls
        case .autoplay: return .autoplay
        case .muted: return .muted
        case .loop: return .loop
        case .playsinline: return .playsinline
        default: break
        }

        let value = value.removingSurroundingQuotes

        guard value != "" else {
            return nil
        }
        switch key {
        case .height:
            guard let height = Int(value) else {
                return nil
            }
            return .height(height)

        case .width:
            guard let width = Int(value) else {
                return nil
            }
            return .width(width)
        case .preload:
            guard let preloadOption = Option.Preload(rawValue: value) else {
                return nil
            }
            return .preload(preloadOption)
        case .poster:
            guard let image = content.image(value) else {
                results.missing(file: value, source: "Video Block: poster")
                return nil
            }
            let width = 2 * content.settings.pages.contentWidth
            let version = image.imageVersion(width: width, height: width, type: .jpg)
            results.require(image: version)
            return .poster(image: version.outputPath)
        case .src:
            guard let file = content.file(value) else {
                results.missing(file: value, source: "Video Block: src")
                return nil
            }
            results.warning("Use 'h264' and 'h265' instead of 'src'")
            return .src(file.absoluteUrl)
        default:
            return nil
        }
    }
}

extension VideoBlock {

    enum Key: String {

        /// The H264 video file to use
        case h264

        /// The H265 video file to use
        case h265

        /// The WebM video file to use
        case webm

        // MARK: Video options

        /// Specifies that video controls should be displayed (such as a play/pause button etc).
        case controls

        /// Specifies that the video will start playing as soon as it is ready
        case autoplay

        /// Specifies that the video will start over again, every time it is finished
        case loop

        /// Specifies that the audio output of the video should be muted
        case muted

        /// Mobile browsers will play the video right where it is instead of the default, which is to open it up fullscreen while it plays
        case playsinline

        /// Sets the height of the video player
        case height

        /// Sets the width of the video player
        case width

        /// Specifies if and how the author thinks the video should be loaded when the page loads
        case preload

        /// Specifies an image to be shown while the video is downloading, or until the user hits the play button
        case poster

        /// Specifies the URL of the video file
        case src

        var isOption: Bool {
            switch self {
            case .controls, .autoplay, .loop, .muted, .playsinline, .height, .width, .preload, .poster, .src:
                return true
            default:
                return false
            }
        }

        var sourceType: SourceType? {
            switch self {
            case .h264: .h264
            case .h265: .h265
            case .webm: .webm
            default: nil
            }
        }
    }

    enum SourceType {
        case h264
        case h265
        case webm

        var order: Int {
            switch self {
            case .h265: 1
            case .webm: 2
            case .h264: 3
            }
        }

        var mimeType: String {
            switch self {
            case .h265: "video/mp4; codecs=\"hvc1\""
            case .h264: "video/mp4; codecs=\"avc1\""
            case .webm: "video/webm"
            }
        }

        static func h265(codec: String) -> SourceType? {
            switch codec {
            case "hvc1": return .h265
            case "avc1": return .h264
            default: return nil
            }
        }
    }

    struct Source {

        let file: FileResource

        let type: SourceType
    }
}

extension VideoBlock {

    enum Option {

        /// Specifies that video controls should be displayed (such as a play/pause button etc).
        case controls

        /// Specifies that the video will start playing as soon as it is ready
        case autoplay

        /// Specifies that the video will start over again, every time it is finished
        case loop

        /// Specifies that the audio output of the video should be muted
        case muted

        /// Mobile browsers will play the video right where it is instead of the default, which is to open it up fullscreen while it plays
        case playsinline

        /// Sets the height of the video player
        case height(Int)

        /// Sets the width of the video player
        case width(Int)

        /// Specifies if and how the author thinks the video should be loaded when the page loads
        case preload(Preload)

        /// Specifies an image to be shown while the video is downloading, or until the user hits the play button
        case poster(image: String)

        /// Specifies the URL of the video file
        case src(String)

        var rawValue: String {
            switch self {
            case .controls: return "controls"
            case .autoplay: return "autoplay"
            case .muted: return "muted"
            case .loop: return "loop"
            case .playsinline: return "playsinline"
            case .height(let height): return "height='\(height)'"
            case .width(let width): return "width='\(width)'"
            case .preload(let option): return "preload='\(option)'"
            case .poster(let image): return "poster='\(image)'"
            case .src(let url): return "src='\(url)'"
            }
        }
    }
}

extension VideoBlock.Option {

    /**
     The `preload` attribute specifies if and how the author thinks that the video should be loaded when the page loads.

     The `preload` attribute allows the author to provide a hint to the browser about what he/she thinks will lead to the best user experience.
     This attribute may be ignored in some instances.

     Note: The `preload` attribute is ignored if `autoplay` is present.
     */
    enum Preload: String, CaseIterable {

        /// The author thinks that the browser should load the entire video when the page loads
        case auto

        /// The author thinks that the browser should load only metadata when the page loads
        case metadata

        /// The author thinks that the browser should NOT load the video when the page loads
        case none
    }
}
