"""
    scan_x(scanner, x_range, x_num_scans; kwargs..)
    scan_y(scanner, y_range, y_num_scans; kwargs..)
    scan_z(scanner, z_range, z_num_scans; kwargs..)

Move along a single axis and grab data based on a fixed increment

# Arguments
- `scanner::IntensityScan`: the structure for all controller connections.
- `x_range`: An array or tuple that contains the start and end 
             point of the desired range in the x-direction.
             (Must be 1 or 2 elements long)
- `x_num_scans::Int`: The number of points/scans the user desires 
                      to record along the x-axis.

# Keywords
- `verbose`::Bool: Optional, defaults to: true

# Returns
- `ScanData`

# Example
```
ps = initialize(PositionerSystem)
scope = initialize(AgilentDSOX4034A)

scanner = IntensityScan(
    xyz = ps, 
    scope = scope, 
    channel = 1, 
    post_move_delay = 0ms,
    precapture_delay = 0µs,
)

wave_x = scan_x(scanner, [-10mm, 10mm], 5)
```
"""
function scan_x(scanner::IntensityScan, x_range, num_points; verbose=true)
    scan_data = scan_single_axis(scanner, x_range, num_points, move_x_abs; verbose=verbose)
    return scan_data
end


function scan_y(scanner, y_range, num_points; verbose=true)
    scan_data = scan_single_axis(scanner, y_range, num_points, move_y_abs; verbose=verbose)
    return scan_data
end


function scan_z(scanner, z_range, num_points; verbose=true)
    scan_data = scan_single_axis(scanner, z_range, num_points, move_z_abs; verbose=verbose)
    return scan_data
end


function scan_single_axis(scanner, axis_range, num_scans, move_func; verbose=true)
    axis = get_axis(move_func)
    check_xyz_limits(scanner, axis, axis_range)
    positions = create_positions_vector(axis_range, num_scans)
    
    # construct the ScanData struct
    scope_info = TcpInstruments.scope_waveform_info_get(scanner.scope, scanner.channel)
    dummy_data = TcpInstruments.scope_parse_raw_waveform(zeros(scope_info.num_points), scope_info)
    time_points = dummy_data.time
    scan_data = ScanData(axis, scope_info, time_points, scope_info.num_points, [num_scans])

    pm = Progress(num_scans; desc="Scanning $axis-direction: ", enabled=verbose)
    for scan_index in 1:num_scans
        move_func(scanner.xyz, positions[scan_index])
        sleep(raw(scanner.post_move_delay))
        ProgressMeter.update!(pm)

        scanner.trigger_function()

        sleep(raw(scanner.precapture_delay))
        data = get_data(scanner.scope, scanner.channel)
        scan_data.waveform[:, scan_index] = data.volt
        scan_data.coordinates[:, scan_index] = get_pos(scanner.xyz)

        ProgressMeter.next!(pm)
    end

    return scan_data
end

function create_positions_vector(axis_range, num_points)
    if length(axis_range) == 1
        if num_points != 1
            error(
                "Can't measure multiple points when axis_range has one position"
            )
        end
        return axis_range
    elseif length(axis_range) == 2
        return range(axis_range[begin], stop=axis_range[end], length=num_points)
    else
        error("axis_range must have 1 or 2 elements")
    end
end

function check_xyz_limits(
    scanner::IntensityScan, axis::AbstractString, axis_range::Array
)
    if axis == "x"
        check_xyz_limits(get_limits_x(scanner.xyz), axis_range)
    elseif axis == "y"
        check_xyz_limits(get_limits_y(scanner.xyz), axis_range)
    elseif axis == "z"
        check_xyz_limits(get_limits_z(scanner.xyz), axis_range)
    else
        error("Single axis must be x, y or z not: $axis")
    end
end

function check_xyz_limits(limits, axis_range::Array)
    low_limit, high_limit = limits
    for range in axis_range
        if range < low_limit || range > high_limit
            error("Scan range ($range) is outside current limits of the xyz stage ($low_limit - $high_limit)")
        end
    end
end

get_axis(move_func::Function) = split(string(move_func), '_')[2] 
