mutable struct Header
    filename::String
    parent_filename::String
    comments1::String
    comments2::String
    energy_type::Int16
    config_type::Int16
    file_type::Int16
    trans_type::Int16
    scan_type::Int16
    data_type::Int16
    date_modified::String
    frequency::Float32
    mat_velocity::Float32
    num_pts::Int32
    num_polarization_channels::Int16
    spare00::Int16
    adc_min_voltage::Float32
    adc_max_voltage::Float32
    band_width::Float32
    spare01::Array{Int16}
    polarization_type::Array{Int16}
    record_header_size::Int16
    word_type::Int16
    word_precision::Int16
    min_data_value::Float32
    max_data_value::Float32
    avg_data_value::Float32
    data_scale_factor::Float32
    data_units::Int16
    surf_removal::UInt16
    edge_weighting::UInt16
    x_units::UInt16
    y_units::UInt16
    z_units::UInt16
    t_units::UInt16
    spare02::Int16
    x_return_speed::Float32
    y_return_speed::Float32
    z_return_speed::Float32
    scan_orientation::Int16
    scan_direction::Int16
    data_storage_order::Int16
    scanner_type::Int16
    x_inc::Float32
    y_inc::Float32
    z_inc::Float32
    t_inc::Float32
    num_x_pts::Int32
    num_y_pts::Int32
    num_z_pts::Int32
    num_t_pts::Int32
    x_speed::Float32
    y_speed::Float32
    z_speed::Float32
    x_acc::Float32
    y_acc::Float32
    z_acc::Float32
    x_motor_res::Float32
    y_motor_res::Float32
    z_motor_res::Float32
    x_encoder_res::Float32
    y_encoder_res::Float32
    z_encoder_res::Float32
    date_processed::String
    time_processed::String
    depth_recon::Float32
    x_max_travel::Float32
    y_max_travel::Float32
    elevation_offset_angle::Float32
    roll_offset_angle::Float32
    z_max_travel::Float32
    azimuth_offset_angle::Float32
    adc_type::Int16
    spare06::Int16
    scanner_radius::Float32
    x_offset::Float32
    y_offset::Float32
    z_offset::Float32
    t_delay::Float32
    range_gate_start::Float32
    range_gate_end::Float32
    ahis_software_version::Float32
    spare_end::Array{Int16}
    Header() = new()
end

function read_header(infile::String)
    h = Header()
    fid = open(infile, "r")

    h.filename = read(fid, 20)
    h.parent_filename = read(fid, 20)
    h.comments1 = read(fid, 80)
    h.comments2 = read(fid, 80)
    h.energy_type = read(fid, Int16)
    h.config_type = read(fid, Int16)
    h.file_type = read(fid, Int16)
    h.trans_type = read(fid, Int16)
    h.scan_type = read(fid, Int16)
    h.data_type = read(fid, Int16)
    h.date_modified = read(fid, 16)
    h.frequency = read(fid, Float32)
    h.mat_velocity = read(fid, Float32)
    h.num_pts = read(fid, Int32)
    h.num_polarization_channels = read(fid, Int16)
    h.spare00 = read(fid, Int16)
    h.adc_min_voltage = read(fid, Float32)
    h.adc_max_voltage = read(fid, Float32)
    h.band_width = read(fid, Float32)
    h.spare01 = read(fid, Int16, 5)
    h.polarization_type = read(fid, Int16, 4)
    h.record_header_size = read(fid, Int16)
    h.word_type = read(fid, Int16)
    h.word_precision = read(fid, Int16)
    h.min_data_value = read(fid, Float32)
    h.max_data_value = read(fid, Float32)
    h.avg_data_value = read(fid, Float32)
    h.data_scale_factor = read(fid, Float32)
    h.data_units = read(fid, Int16)
    h.surf_removal = read(fid, UInt16)
    h.edge_weighting = read(fid, UInt16)
    h.x_units = read(fid, UInt16)
    h.y_units = read(fid, UInt16)
    h.z_units = read(fid, UInt16)
    h.t_units = read(fid, UInt16)
    h.spare02 = read(fid, Int16)
    h.x_return_speed = read(fid, Float32)
    h.y_return_speed = read(fid, Float32)
    h.z_return_speed = read(fid, Float32)
    h.scan_orientation = read(fid, Int16)
    h.scan_direction = read(fid, Int16)
    h.data_storage_order = read(fid, Int16)
    h.scanner_type = read(fid, Int16)
    h.x_inc = read(fid, Float32)
    h.y_inc = read(fid, Float32)
    h.z_inc = read(fid, Float32)
    h.t_inc = read(fid, Float32)
    h.num_x_pts = read(fid, Int32)
    h.num_y_pts = read(fid, Int32)
    h.num_z_pts = read(fid, Int32)
    h.num_t_pts = read(fid, Int32)
    h.x_speed = read(fid, Float32)
    h.y_speed = read(fid, Float32)
    h.z_speed = read(fid, Float32)
    h.x_acc = read(fid, Float32)
    h.y_acc = read(fid, Float32)
    h.z_acc = read(fid, Float32)
    h.x_motor_res = read(fid, Float32)
    h.y_motor_res = read(fid, Float32)
    h.z_motor_res = read(fid, Float32)
    h.x_encoder_res = read(fid, Float32)
    h.y_encoder_res = read(fid, Float32)
    h.z_encoder_res = read(fid, Float32)
    h.date_processed = read(fid, 8)
    h.time_processed = read(fid, 8)
    h.depth_recon = read(fid, Float32)
    h.x_max_travel = read(fid, Float32)
    h.y_max_travel = read(fid, Float32)
    h.elevation_offset_angle = read(fid, Float32)
    h.roll_offset_angle = read(fid, Float32)
    h.z_max_travel = read(fid, Float32)
    h.azimuth_offset_angle = read(fid, Float32)
    h.adc_type = read(fid, Int16)
    h.spare06 = read(fid, Int16)
    h.scanner_radius = read(fid, Float32)
    h.x_offset = read(fid, Float32)
    h.y_offset = read(fid, Float32)
    h.z_offset = read(fid, Float32)
    h.t_delay = read(fid, Float32)
    h.range_gate_start = read(fid, Float32)
    h.range_gate_end = read(fid, Float32)
    h.ahis_software_version = read(fid, Float32)
    h.spare_end = read(fid, Int16, 10) # Official code says Float32 but this should be Int16

    close(fid)
    return h
end

function read_data(infile::String)
    extension = splitext(infile)[2]
    h = read_header(infile)
    nx = h.num_x_pts
    ny = h.num_y_pts
    nt = h.num_t_pts
    
    # Determine word type
    if h.word_type == 7
        T = Float32
    elseif h.word_type == 4
        T = UInt16
    else
        throw(ErrorException("Invalid word_type found in file - expected 7 for 32-bit float or 4 for 16-bit unsigned int"))
    end

    fid = open(infile, "r")
    seek(fid, 512) # skip header
    if extension == ".aps" || extension == ".a3daps"
        data = reshape(read(fid, T, nx*ny*nt), (nx, ny, nt))
    elseif extension == ".a3d"
        data = reshape(read(fid, T, nx*nt*ny), (nx, nt, ny))
    elseif extension == ".ahi"
        data = reshape(read(fid, T, 2*nx*ny*nt), (2, ny, nx, nt))
    else
        throw(ErrorException("Invalid file extension. Valid formats are aps, a3d, a3daps, and ahi"))
    end
    close(fid)

    data *= h.data_scale_factor

    return data
end