% MATLAB test for XIMC library
% Tested R2014b 32-bit WinXP, R2014b 64-bit Win7, R2014b 64-bit OSX 10.10

function main()
    clc;
    clear;
    
    [~, maxArraySize] = computer;
    is64bit = maxArraySize > 2^31;
    if (ispc)
        if (is64bit)
            disp('Using 64-bit version');
        else
            disp('Using 32-bit version');
        end
    elseif ismac
        disp('Using Mac version');
    elseif isunix
        disp('Using Unix version');
    end

    if not(libisloaded('libximc'))
        fprintf('Loading library...');
        if ispc
            addpath(fullfile(pwd, '../../ximc/win64/wrappers/matlab/'));
            if (is64bit)
                    addpath(fullfile(pwd, '../../ximc/win64/'));
                    [notfound, warnings] = loadlibrary('libximc.dll', @ximcm);
            else
                    addpath(fullfile(pwd, '../../ximc/win32/'));
                    [notfound, warnings] = loadlibrary('libximc.dll', 'ximcm.h', 'addheader', 'ximc.h');
            end
        elseif ismac
            addpath(fullfile(pwd, '../../ximc/'));
            [notfound, warnings] = loadlibrary('libximc.framework/libximc', 'ximcm.h', 'mfilename', 'ximcm.m', 'includepath', 'libximc.framework/Versions/Current/Headers', 'addheader', 'ximc.h');
        elseif isunix
            [notfound, warnings] = loadlibrary('libximc.so', 'ximcm.h', 'addheader', 'ximc.h');
        end
        fprintf(' Done\n');
    else
        disp('Library loaded');
    end
    
    version_buffer = blanks(32);
    ximc_version_str = calllib('libximc', 'ximc_version', version_buffer);
    fprintf('This is a ximc test program\nlibximc version %s\n', ximc_version_str);
    
    if warn_user_about_modifications()
        return;
    end

    device_name = choose_device();
    fprintf('\nOpening device...\n');
    device = calllib('libximc', 'open_device', device_name);
    device_undefined = -1;
    if (isnumeric(device) && device == device_undefined) || (isstruct(device) && isNull(device))
        fprintf('Error opening device\nPress Enter to exit...');
        input('', 's');
        return; 
    end
    
    print_serial_number(device);
    set_microstep_256(device);
    state = get_status(device);
    disp('Status:');
    disp(state);

    start_position = state.CurPosition;
    start_uposition = state.uCurPosition;
    fprintf('Current position: %d steps, %d mictosteps\n', start_position, start_uposition);
    
    disp('Running engine to the right for 5 seconds...');
    result = calllib('libximc', 'command_right', device);
    if result ~= 0
        fprintf('Command command_right failed with code: %d\n', result);
    end
    pause(5);
    
    calb = struct();
    calb.A = 0.1; % arbitrary choice for example, set by user in real scenarios
    calb.MicrostepMode = 9; % == MICROSTEP_MODE_FRAC_256
    state_calb = get_status_calb(device, calb);
    disp('Status calb:');
    disp(state_calb);

    state = get_status(device);
    disp('Status:');
    disp(state);

    disp('Move back ...');
    result = calllib('libximc', 'command_move', device, start_position, start_uposition);
    if result ~= 0
        fprintf('Command command_move failed with code: %d\n', result);
    end

    disp('Waiting for stop...');
    result = calllib('libximc', 'command_wait_for_stop', device, 100);
    if result ~= 0
        fprintf('Command command_wait_for_stop failed with code: %d\n', result);
    end

    state = get_status(device);
    disp('Status:');
    disp(state);
    
    fprintf('Closing... ');
    device_ptr = libpointer('int32Ptr', device);
    calllib('libximc', 'close_device', device_ptr);
    fprintf(' Done\n');
end

function device_name = choose_device()
    [devenum, device_count] = search_devices();
    
    if device_count < 1
        fprintf('The real controller is not found or busy with another app.\n');
        fprintf('The virtual controller is opening to check the operation of the library...\n');
        device_name = create_virtual_device_name();
    else
        device_name = get_device_name_from_list(devenum, device_count);
    end

    if ~isempty(devenum) && ~isNull(devenum)
        calllib('libximc', 'free_enumerate_devices', devenum);
    end
end

function [devenum, device_count] = search_devices()
    fprintf('\nSearching for devices...\n');

    ENUMERATE_PROBE = 1;
    ENUMERATE_NETWORK = 4;
    probe_flags = bitor(ENUMERATE_PROBE, ENUMERATE_NETWORK);
    enumerate_hints = 'addr=';    
    devenum = calllib('libximc', 'enumerate_devices', probe_flags, enumerate_hints);
    
    if isempty(devenum) || isNull(devenum)
        fprintf('Error searching for devices\n');
        device_count = 0;
        return;
    end
    
    device_count = calllib('libximc', 'get_device_count', devenum);
    if device_count < 1
        fprintf('No devices found\n');
        return;
    end
    
    fprintf('Found %d devices\n', device_count + 1);
end

function print_device_list(devenum, device_count)
    fprintf('\nFound devices list:\n');
    for i = 1:device_count
        name = calllib('libximc', 'get_device_name', devenum, i - 1);
        fprintf(' #%d. %s\n', i, name);
    end
    fprintf(' #%d. virtual\n', device_count + 1);
end

function device_name = create_virtual_device_name()
    if ispc
        home_dir = getenv('USERPROFILE');
        path_sep = '\';
    else
        home_dir = getenv('HOME');
        path_sep = '/';
    end
    
    virt_path = [home_dir, path_sep, 'virt_controller.bin'];
    fprintf('Virtual controller file path: ''%s''\n', virt_path);
    
    device_name = ['xi-emu:///', virt_path];
end

function device_name = get_device_name_from_list(devenum, device_count)
    while true
        print_device_list(devenum, device_count);
        
        fprintf(['When using an Ethernet (TCP protocol) connection, multi-user interaction with the device is possible. ' ...
                 'In this case, the same device may be controlled simultaneously from multiple sources, ' ...
                 'which can potentially lead to control conflicts.\n']);
        
        reply = input('Enter device number from the list:\n', 's');
        number = str2double(reply);
        
        if isnan(number)
            continue;
        end
        
        if number >= 1 && number <= device_count
            device_name = calllib('libximc', 'get_device_name', devenum, number - 1);
            fprintf('Selected device: ''%s''\n', device_name);
            return;
        end
        
        if number == device_count + 1
            device_name = create_virtual_device_name();
            return;
        end
    end
end

function res = isNull(ptr)
    res = false;
    try
        res = islogical(ptr.IsNull) && ptr.IsNull;
    catch
    end
end

function print_serial_number(device)
    serial_number = 0;
    serial_number_ptr = libpointer('uint32Ptr', serial_number);
    [result, serial] = calllib('libximc','get_serial_number', device, serial_number_ptr);
    clear serial_number_ptr;
    if result ~= 0
        fprintf('Command get_serial_number failed with code: %d\n', result);
    else
        fprintf('Serial number: %d\n', serial);
    end
end

function set_microstep_256(device)
    fprintf('\nSet microstep mode to 256...\n');
    % To use the libximc library commands get_engine_settings, set_engine_settings,
    % you need to initialize the engine_settings_t structure used by commands.
    % Here is a trick.
    % It is necessary to initiate an arbitrary structure(dummy_struct) with a field that is present in the structure engine_settings_t.
    % Based on which the engine_settings_t structure should be set.
    dummy_struct = struct('MicrostepMode', 0);
    parg_struct = libpointer('engine_settings_t', dummy_struct);
    [result, engine_settings] = calllib('libximc', 'get_engine_settings', device, parg_struct);

    clear parg_struct
    if result ~= 0
        fprintf('Command get_engine_settings failed with code: %d\n', result);
    end

    % 9 is the value of the constant MICROSTEP_MODE_FRAC_256
    % See all MICROSTEP_MODE_FRAC_ constants in documentation
    % For example, you should write 5 instead of 9 for MICROSTEP_MODE_FRAC_16
    engine_settings.MicrostepMode = 9;
    result = calllib('libximc', 'set_engine_settings', device, engine_settings);
    if result ~= 0
        fprintf('Command set_engine_settings failed with code: %d\n', result);
    end
end

function [ res_struct ] = get_status(device)
    % To use the libximc library command get_status,
    % you need to initialize the status_t structure used by the commands.
    % Here is a trick.
    % It is necessary to initiate an arbitrary structure(dummy_struct) with a field that is present in the structure status_t.
    % Based on which the status_t structure should be set.
    dummy_struct = struct('Flags', 999);
    parg_struct = libpointer('status_t', dummy_struct);
    [result, res_struct] = calllib('libximc', 'get_status', device, parg_struct);
    clear parg_struct;
    if result ~= 0
        fprintf('Command get_status failed with code: %d\n', result);
        res_struct = 0;
    end
end

function [ res_struct ] = get_status_calb(device, calibration)
    % To use the libximc library command get_status_calb,
    % you need to initialize the status_calb_t structure used by the commands.
    % Here is a trick.
    % It is necessary to initiate an arbitrary structure(dummy_struct) with a field that is present in the structure status_calb_t.
    % Based on which the status_calb_t structure should be set.
    dummy_struct = struct('Flags', 999);
    parg_struct = libpointer('status_calb_t', dummy_struct);
    parg2_struct = libpointer('calibration_t', calibration);
    [result, res_struct] = calllib('libximc','get_status_calb', device, parg_struct, parg2_struct);
    clear parg_struct;
    clear parg2_struct;
    if result ~= 0
        fprintf('Command get_status_calb failed with code: %d\n', result);
        res_struct = 0;
    end
end

function should_exit = warn_user_about_modifications()
    while true
        fprintf('\nThe script will modify the following controller settings:\n');
        fprintf(' engine settings\n');
        answer = input('Continue? [y/n]: ', 's');
        answer_lower = lower(answer);
        if strcmp(answer_lower, 'y')
            should_exit = false;
            return;
        end
        
        if strcmp(answer_lower, 'n')
            should_exit = true;
            return;
        end
    end
end
