"""
    cuparse(raw::CDataRaw; dim="2Dsym")
    cuparse(path::String)

**Keyword Arguments**
- `mode::String`: "dset", "table" _(, "auto")_

!!! note "COMSOL"
    Latest tested version: 5.3.0.223
"""
function cuparse(raw::CDataRaw; dim="2Dsym", mode="auto")
    mode = mode == "auto" ? _autodetectmode(raw.field) : mode

    # switch between the data mode
    return mode == "dset" ? cuparse_dset(raw, dim) :
           mode == "table" ? cuparse_table(raw, dim) :
           error("Keyword mode with unsupported argument \"$mode\". ")
end
cuparse(path::String; dim="2Dsym") = cuparse(curead(path); dim=dim)


"""
    cuparse_dset(raw::CDataRaw, dim::String)
"""
function cuparse_dset(raw::CDataRaw, dim::String)
    n = String[]; u = String[]; t = Float64[]; v = Dict{String, Any}(); # init vectors

    N, _ = _parsedim(dim) # dimension
    for i in N+1:length(raw.field)
        name, unit, time = _readfieldall(raw.field[i]) # extract data from field
        v[name] = haskey(v, name) ? hcat(v[name], raw.v[raw.field[i]]) : raw.v[raw.field[i]] # values (Dict)
        push!(t, time) # time

        if name ∈ n; continue; end # name MUST be unique
        push!(n, name); push!(u, unit); # name and unit
    end

    t = sort(unique(t)) # time
    p = _parseposition(raw.v, dim) # position

    return CDataDSet(raw.path, raw.meta, n, u, t, p, v)
end


"""
    cuparse_table(raw::CDataRaw, dim::String)
"""
function cuparse_table(raw::CDataRaw, dim::String)
    n = String[]; u = String[]; v = Dict{String, Any}(); # init vectors

    old_keys = collect(keys(raw.v)); # old keys from raw dictionary

    for i in 1:length(raw.field)
        new_key, unit = _separatenameunit(old_keys[i]); # split field
        push!(n, new_key); push!(u, unit); # push name and unit
        v[new_key] = raw.v[old_keys[i]]; # add to Dict
    end

    return CDataTable(raw.path, raw.meta, n, u, v)
end


## INTERNAL FUNCTION ======================================================== ##
function _autodetectmode(field::Vector{String})
    sum([occursin("@", field[i]) ? 1 : 0 for i in length(field)]) > 0 ? "dset" : "table"
end

function _readfieldall(s::String)
    n, u, _, t = split(s, " ") # expected format: `<name> (<unit>) @ t=<time>`
    return n, u[2:end-1], parse(Float64, t[3:end])
end

function _separatenameunit(s::String)
    for i in length(s):-1:1
        if s[i] == '('
            return s[1:i-2], s[i+1:end-1]
        end
    end
end

function _parsedim(dim::String)
    if dim == "2Dsym"
        return 2, ("R", "Z")
    end
end

function _parseposition(v::Dict{String, Any}, dim::String)
    if dim == "2Dsym"
        return CPosition2Dsym(v["R"], v["Z"])
    end
end
