"""
    run_yaml(filename::String)
    run_yaml(filenames::Array{String,1})
This file runs (a) yaml file(s) if it includes the line `Editable: true`. If given a list of
input files, the function will print a list of yaml files that were not marked as editable.
A file might not be "editable" if SLiDE functionality cannot make all of the specified edits.

# Arguments
- `filename::String` or `filenames::Array{String,1}`: yaml file name (or list of propertynames) to run

# Return
- `filename::String` or `filenames::Array{String,1}`: yaml file name (or list of propertynames) that
    was/were not ran by the function because they were annotated with `Editable: false`
"""
function run_yaml(filename::String; save::Bool=true)
    println("\n$filename")
    println("  Reading yaml file...")

    y = read_file(filename)

    if haskey(y, "Editable") && y["Editable"]
        # Iteratively make all edits and update output data file in accordance with
        # operations defined in the input YAML file.
        println("  Parsing and standardizing...")
        df = unique(edit_with(y; print_status=true))
        _save_datastream(df, y; save=save)
        return df
    else
        println("  Skipping... (not editable)")
        return filename
    end
end


function run_yaml(lst::AbstractArray; save::Bool=true)
    return Dict(_inp_key(x) => run_yaml(x; save=save) for x in lst)
end


function run_yaml(files::Array{String,1}; save::Bool=true)
    @info("Standardize data files.")
    files = Dict(_inp_key(f) => run_yaml(f) for f in files)

    # Print message indicating which, if any, files could not be executed.
    unedited = [f for f in values(files) if typeof(f) == String]
    if length(unedited) > 0
        @warn(string("run_yaml() generated no output for:", string.("\n  ", unedited)...,
            "\nAdd \"Editable: true\" to yaml file to run automatically."))
    end

    # !!!! Would be nice to track output directories created (output from _save_datastream)
    # and delete after all of the edits in a directory have been made.

    return files
end


"""
    write_yaml(path, file::XLSXInput)
This function reads an XLSX file and writes a new yaml file containing the information in
each spreadsheet column. Sheet propertynames in the XLSX file correspond to the directory where new
files will be printed (`path/file.sheet/`). Yaml files will be named after the text in the
column's first row.

# Arguments
- `path::String` or `path::Array{String,1}`: XLSX file location. New yaml files will be
    printed here, as well.
- `file::XLSXInput` or `files::Array{XLSXInput,1}`: XLSX file information (file name, sheet name, sheet range) or list of multiple sheets.

# Returns
- `filenames::Array{String,1}`: List of yaml files
"""
function write_yaml(path, file::XLSXInput; overwrite::Bool=true)
    @info("Generating yaml files from " * file.name * "\n\tSheet: " * file.sheet)

    # Aesthetics: Define a message to print at the top of the YAML file and
    # list all key words in the yaml file and use to add (purely aesthetic) spacing.
    HEADER = "# Autogenerated from " * joinpath(path, file.name) * ", sheet: " * file.sheet
    KEYS = string.([IU.subtypes.(IU.subtypes(DataStream))...; "PathIn"], ":")
    
    # Read the XLSX file, identify relevant (not "missing") columns,
    # and generate list of resultant yaml file propertynames.
    println("  Reading ", file.name)
    df = read_file(path, file)
    df = df[:, .!occursin.(:missing, propertynames(df))]

    # Make sure the path exists and save the names of yaml files to generaate.
    # path = joinpath(SLIDE_DIR, path, file.sheet)
    path = joinpath(path, "readfiles", file.sheet)
    !isdir(path) && mkpath(path)

    files = [joinpath(path, "$k.yml") for k in propertynames(df)]
    
    # Iterate through columns. For each column, create a new yaml file and fill it with
    # the column text. Mark the yaml file as "Autogenerated" and ensure one space only
    # between each input file element that corresponds with a SLiDE Datastream subtype.
    for (col, yamlfile) in zip(propertynames(df), files)

        if overwrite == false && isfile(yamlfile)
            println("  Skipping overwrite. Found: $yamlfile")
        else
            println("  Generating $yamlfile")

            lines = dropmissing(df, col)[:,col]
            lines = lines[match.(r"\S.*", lines) .!== nothing]

            open(yamlfile, "w") do f
                println(f, HEADER)
                for line in lines
                    any(occursin.(KEYS, line)) && (println(f, ""))
                    println(f, line)
                end
            end
        end
    end
    return files
end


function write_yaml(path::Array{String,1}, file::XLSXInput; overwrite::Bool=true)
    write_yaml(joinpath(path...), file; overwrite=overwrite)
end


function write_yaml(path, files::Array{XLSXInput,1}; overwrite::Bool=true)
    if length(unique(get_descriptor.(files))) < length(files)
        @error("Input XLSX files must have unique descriptors.")
    end
    # files = Dict(_inp_key(file) => file for file in files)
    # files = Dict(k => write_yaml(path, file; overwrite=overwrite) for (k,file) in files)
    return [write_yaml(path, file; overwrite=overwrite) for file in files]
end


"""
"""
function _save_datastream(df::DataFrame, y::Dict; save=true)
    save_path = save == true ? "" : save
    haskey(y, "Temporary")   && (y["FileOut"] = _temporary(y["FileOut"]))
    haskey(y, "Development") && (y["FileOut"] = _development(y["FileOut"]))

    path = joinpath(save_path, ensurearray(y["PathOut"])...)
    file = joinpath(path, y["FileOut"])
    !isdir(path) && mkpath(path)

    println("  Saving $file...")
    CSV.write(file, df)
    return path
end


_temporary(x::String) = "__" * x
_development(x::String) = "_" * x


_isdevelopment(x::String) = match(r"^_[^_]", x) !== nothing
_istemporary(x::String) = match(r"^_{2}[^_]", x) !== nothing


"""
Delete temporary files. These are pre-pended "_".
"""
function _delete_temporary(path::String)
    curr_dir = pwd()
    files = readdir(path)
    files = files[SLiDE._istemporary.(files)]
    cd(path)
    [rm(file) for file in files]
    cd(curr_dir)
    return nothing
end