let splitString (s : string) = 
    s.Split([|' '|])

type Token = 
    |SOURCE
    |REGISTRY
    |ZIP
    |SERVICE
    |DESTINATION
    |MAXDAYS
    |SMTPSERVER
    |FROMEMAIL
    |SUCCESSEMAIL
    |FAILUREEMAIL
    |VALUE of string

let tokenize (args : string[]) = 
    [for x in args do
        let token = 
            match x with
            | "-o" -> SOURCE
            | "-r" -> REGISTRY
            | "-z" -> ZIP
            | "-v" -> SERVICE
            | "-d" -> DESTINATION
            | "-m" -> MAXDAYS
            | "-t" -> SMTPSERVER
            | "-e" -> FROMEMAIL
            | "-s" -> SUCCESSEMAIL
            | "-f" -> FAILUREEMAIL
            | _ -> VALUE x
        yield token]

type Zip = ZipFiles | DoNotZipFiles

type Options = {
    Source : string;
    Registry : string;
    Zip : Zip;
    Service : string;
    Destination : string;
    MaxDays : int;
    SmtpServer : string;
    FromEmail : string;
    SuccessEmail : string;
    FailureEmail : string;
    }

let isWholeNumber s = String.forall (fun c -> System.Char.IsDigit(c)) s

// Strips VALUE tokens from top of list, returning the rest of the list
let returnNonValueTail tokenList =
    tokenList
    |>List.toSeq
    |>Seq.skipWhile (fun t -> match t with VALUE y -> true | _ -> false)
    |>Seq.toList

// Takes VALUE tokens from the top of the list, contatenates their associated strings and returns the contatenated string.
let returnConcatHeadValues tokenList =
    tokenList
    |> List.toSeq
    |> Seq.takeWhile (fun t -> match t with VALUE y -> true | _ -> false)
    |> Seq.fold (fun acc elem -> match elem with VALUE y -> acc + " " + y | _ -> acc) " "
    |> fun s -> s.Trim()

let rec parseTokenListRec tokenList optionsSoFar =
    match tokenList with
    | [] -> optionsSoFar
    | SOURCE::t -> 
        match t with
        | VALUE x::tt -> parseTokenListRec (returnNonValueTail t) {optionsSoFar with Source = (returnConcatHeadValues t)}
        | _ -> failwith "Expected a value after the source argument."
    | REGISTRY::t ->
        match t with
        | VALUE x::tt -> parseTokenListRec (returnNonValueTail t) {optionsSoFar with Registry = (returnConcatHeadValues t)}
        | _ -> failwith "Expected a value after the registry argument."
    | ZIP::t -> parseTokenListRec t {optionsSoFar with Zip = ZipFiles}
    | SERVICE::t ->
        match t with
        | VALUE x::tt -> parseTokenListRec (returnNonValueTail t) {optionsSoFar with Service = (returnConcatHeadValues t)}
        | _ -> failwith "Expected a value after the service argument."
    | DESTINATION::t ->
        match t with
        | VALUE x::tt -> parseTokenListRec (returnNonValueTail t) {optionsSoFar with Destination = (returnConcatHeadValues t)}
        | _ -> failwith "Expected a value after the destination argument."
    | MAXDAYS::t ->
        match t with
        |VALUE x::tt when (isWholeNumber x) -> parseTokenListRec tt {optionsSoFar with MaxDays = int x}
        | _ -> failwith "Expected a whole number to be supplied after the maxdays argument."
    | SMTPSERVER::t ->
        match t with
        | VALUE x::tt -> parseTokenListRec (returnNonValueTail t) {optionsSoFar with SmtpServer = (returnConcatHeadValues t)}
        | _ -> failwith "Expected a value after the smtp server argument."
    | FROMEMAIL::t ->
        match t with
        | VALUE x::tt -> parseTokenListRec (returnNonValueTail t) {optionsSoFar with FromEmail = (returnConcatHeadValues t)}
        | _ -> failwith "Expected a value after the from email argument."
    | SUCCESSEMAIL::t ->
        match t with
        | VALUE x::tt -> parseTokenListRec (returnNonValueTail t) {optionsSoFar with SuccessEmail = (returnConcatHeadValues t)}
        | _ -> failwith "Expected a value after the success email argument."
    | FAILUREEMAIL::t ->
        match t with
        | VALUE x::tt -> parseTokenListRec (returnNonValueTail t) {optionsSoFar with FailureEmail = (returnConcatHeadValues t)}
        | _ -> failwith "Expected a value after the failure email argument."
    | VALUE x::t -> failwith (sprintf "Encountered a value ('%s') without an associated argument." x)

let parseArgs args =
    let tokenList = tokenize(args)

    let defaultOptions = {
        Source = "";
        Registry = "";
        Zip = DoNotZipFiles;
        Service = "";
        Destination = "";
        MaxDays = 0;
        SmtpServer = "";
        FromEmail = "";
        SuccessEmail = "";
        FailureEmail = "";
        }

    parseTokenListRec tokenList defaultOptions