classdef CSV_Converter_Episodic_ABF_analysis < matlab.apps.AppBase

    % Properties that correspond to app components
    properties (Access = public)
        UIFigure                       matlab.ui.Figure
        Panel                          matlab.ui.container.Panel
        TextArea                       matlab.ui.control.TextArea
        DoneLabel                      matlab.ui.control.Label
        ConvertamplitudetoDropDown     matlab.ui.control.DropDown
        ConvertamplitudetoDropDownLabel  matlab.ui.control.Label
        ConvertUserEditField           matlab.ui.control.NumericEditField
        valueLabel                     matlab.ui.control.Label
        AnerroroccurredduringtheconversionofLabel  matlab.ui.control.Label
        ConvertionErrorTextArea        matlab.ui.control.TextArea
        DecimalseparatorDropDown       matlab.ui.control.DropDown
        DecimalseparatorDropDownLabel  matlab.ui.control.Label
        PostfixEditField               matlab.ui.control.EditField
        PostfixEditFieldLabel          matlab.ui.control.Label
        HeaderlinesSpinner             matlab.ui.control.Spinner
        HeaderlinesSpinnerLabel        matlab.ui.control.Label
        ConvertfilesButton             matlab.ui.control.Button
        TimeunitDropDown               matlab.ui.control.DropDown
        TimeunitDropDownLabel          matlab.ui.control.Label
        DelimiterDropDown              matlab.ui.control.DropDown
        DelimiterDropDownLabel         matlab.ui.control.Label
        InputdirectoryButton           matlab.ui.control.Button
        Path_Label                     matlab.ui.control.Label
    end

    %%CSV Converter Episodic ABF analysis (02/2024)
    % by Michael Rabenstein
    %Support app of Episodic_ABF_analysis (Rabenstein, Michael, 2024, "Episodic
    %ABF analysis", https://doi.org/10.60507/FK2/LBX1UB, bonndata, V1)
    %Licensed under the BSD-3-Clause license
    %
    %Tool to convert csv files in Episodic_ABF_analysis_v5+ importable txt
    %files. Input structure: Headerlines followed by data matrix. First
    %column will be used as x vector, other columns as different y vectores
    properties (Access = private)
        input_files % Description
        decimal_separator_list = [".", ","]
        delimiter_list = ["tab", "comma", "semi", "space", "bar"]
        input_file_types = ["*.txt", "*.csv", "*.tsv", "*.tab"]; %Create a file list
    end
    

    % Callbacks that handle component events
    methods (Access = private)

        % Code that executes after component creation
        function startupFcn(app)
            movegui(app.UIFigure,'center')
        end

        % Button pushed function: InputdirectoryButton
        function InputdirectoryButtonPushed(app, event)
            %Select export folder
            app.DoneLabel.Visible = "off";
            if isempty(app.Path_Label.Text) %In case no file path was already selected
                userProfile = getenv('USERPROFILE'); %Fetch users default folder...
                myDocsFolder = sprintf('%s\\My Documents', userProfile); %...and suggest documents folder by default
                selpath = uigetdir(myDocsFolder,'Select input folder'); %Ask for directory
            else
                myDocsFolder = app.Path_Label.Text; %Fetch previously selected path as starting point for browsing
                selpath = uigetdir(myDocsFolder,'Select input folder'); %Ask for directory
            end
            figure(app.UIFigure) %Necessary to move app window back to foreground after use of 'uigetfile'
            if selpath == 0 %if no path was selected
                return
            end
            cd (selpath)
            input_files = [];
            input_file_types = app.input_file_types; %Create a file list
            for i_file_type = 1:length(input_file_types)
                input_files_cache = dir(input_file_types(i_file_type));
                for i_files = 1: size(input_files_cache,1)
                    input_files = [input_files; string(input_files_cache(i_files).name)];
                end
            end
            if isempty(input_files) %In case no txt, csv or tsv files are in selected folder
                app.ConvertfilesButton.Enable = "off";
                app.Path_Label.FontColor = [0.77,0.05,0.05];
                app.Path_Label.FontWeight = 'bold';
                app.Path_Label.Text = "No txt, csv or tsv files in selected directory";
                return
            end
            
            input_files(strcmp(input_files,"")) = [];
            app.input_files = input_files;
            app.Path_Label.FontColor = 'black';
            app.Path_Label.FontWeight = 'normal';
            app.Path_Label.Text = selpath; %save and display selected folder
            app.ConvertfilesButton.Enable = "on";
        end

        % Button pushed function: ConvertfilesButton
        function ConvertfilesButtonPushed(app, event)
            app.DoneLabel.Visible = "off";
            app.ConvertionErrorTextArea.Value = "";
            app.AnerroroccurredduringtheconversionofLabel.Visible = "off";
            app.ConvertionErrorTextArea.Visible = "off";
            error_list = [];
            datename = string(datetime('now','Format','uuMMdd''_''HHmmss'));
            if isempty(app.PostfixEditField.Value)
                postfix = '';
            else
                postfix = sprintf('_%s', string(app.PostfixEditField.Value));
            end
            foldername = sprintf('%s_converted%s', datename, postfix);

            newfolder = fullfile(app.Path_Label.Text,foldername);
            mkdir(newfolder)
            input_files = app.input_files;

            if ischar(input_files)
                no_of_files = 1; %If only single file was selected
            else
                no_of_files = length(input_files); %If multiple files were selected
            end

            for i_files = 1:no_of_files
                if ischar(input_files)
                    fn = char(input_files);
                else
                    fn = char(input_files(i_files));
                end
                fn_clean = fn(1:end-4); %remove '.txt/csv/tsv' from filename

                try
                    headerno = 1;
                    headerinfo_cache = [];
                    if app.HeaderlinesSpinner.Value > 0 %Fetch headerlines till defined maximum or first non character line
                        fid = fopen(fn);
                        % headerinfo_cache = [string(fn_clean), ""];
                        headerinfo_cache = [];
                        tline = fgetl(fid);
                        % headerno = 1;
                        while ischar(tline) && headerno <= app.HeaderlinesSpinner.Value
                            headerinfo_cache = [headerinfo_cache; [string(strtrim(tline)), ""]];
                            tline = fgetl(fid);
                            headerno = headerno + 1;
                        end
                        fclose(fid);
                    % else
                    %     headerinfo_cache = [string(fn_clean), ""];
                    end
                    %Load and convert data table
                    decimal_separator = app.decimal_separator_list(app.DecimalseparatorDropDown.Value);
                    delimiter_list = app.delimiter_list(app.DelimiterDropDown.Value);

                    t = readtable(fn, 'Delimiter',delimiter_list, 'DecimalSeparator',decimal_separator, 'VariableNamingRule','preserve', 'HexType','int32','BinaryType','int32', 'NumHeaderLines',(headerno-1));
                    %Convert time to s
                    if app.TimeunitDropDown.Value < 0 %min and h
                        time_convert = (60^app.TimeunitDropDown.Value);
                    else %s and smaller
                        time_convert = 1000^app.TimeunitDropDown.Value;
                    end
                    t{:,1} = t{:,1}/time_convert;

                    %Convert value
                    if app.ConvertamplitudetoDropDown.Value == 50
                        amplitude_convert = app.ConvertUserEditField.Value;
                        if amplitude_convert == 0
                            amplitude_convert = 1;
                        end
                    else
                        amplitude_convert = 10^app.ConvertamplitudetoDropDown.Value;
                    end
                    t{:,2:end} = t{:, 2:end} * amplitude_convert;

                    index_NaN = ~isnan(t{:,1}); %Remove in between headers
                    t_extract = t{index_NaN,1:end}; %Remove in between headers

                    reshaped_sweeps = [];
                    for i_sweeps = 2:size(t_extract, 2)
                        reshaped_sweeps = [reshaped_sweeps; [t_extract(:,1), t_extract(:,i_sweeps)]];
                    end
                    converted_table = array2table([headerinfo_cache;reshaped_sweeps]);
                    export_name = sprintf('%s_converted%s', fn_clean, postfix);
                    export_path = fullfile(foldername,export_name);
                    writetable(converted_table,export_path,'Delimiter','\t', 'WriteVariableNames', 0)
                catch
                    error_list = [error_list;string(fn)]; %List files which weren't compatible
                end
            end
            if ~isempty(error_list)
                app.ConvertionErrorTextArea.Value = error_list;
                app.AnerroroccurredduringtheconversionofLabel.Visible = "on";
                app.ConvertionErrorTextArea.Visible = "on";
            end
app.DoneLabel.Visible = "on";
        end

        % Value changed function: ConvertamplitudetoDropDown
        function ConvertamplitudetoDropDownValueChanged(app, event)
            app.DoneLabel.Visible = "off";
            if app.ConvertamplitudetoDropDown.Value == 50
                app.ConvertUserEditField.Visible = "on";
            else
                app.ConvertUserEditField.Visible = "off";
            end
        end

        % Value changed function: ConvertUserEditField, 
        % ...and 5 other components
        function HeaderlinesSpinnerValueChanged(app, event)
            app.DoneLabel.Visible = "off";            
        end
    end

    % Component initialization
    methods (Access = private)

        % Create UIFigure and components
        function createComponents(app)

            % Create UIFigure and hide until all components are created
            app.UIFigure = uifigure('Visible', 'off');
            app.UIFigure.Position = [100 100 693 273];
            app.UIFigure.Name = 'MATLAB App';
            app.UIFigure.Resize = 'off';

            % Create Panel
            app.Panel = uipanel(app.UIFigure);
            app.Panel.BackgroundColor = [0.902 0.902 0.902];
            app.Panel.Position = [-1 -2 697 277];

            % Create Path_Label
            app.Path_Label = uilabel(app.Panel);
            app.Path_Label.BackgroundColor = [1 1 1];
            app.Path_Label.Position = [129 242 553 22];
            app.Path_Label.Text = '';

            % Create InputdirectoryButton
            app.InputdirectoryButton = uibutton(app.Panel, 'push');
            app.InputdirectoryButton.ButtonPushedFcn = createCallbackFcn(app, @InputdirectoryButtonPushed, true);
            app.InputdirectoryButton.Position = [21 242 100 23];
            app.InputdirectoryButton.Text = 'Input directory';

            % Create DelimiterDropDownLabel
            app.DelimiterDropDownLabel = uilabel(app.Panel);
            app.DelimiterDropDownLabel.HorizontalAlignment = 'right';
            app.DelimiterDropDownLabel.Position = [169 212 52 22];
            app.DelimiterDropDownLabel.Text = 'Delimiter';

            % Create DelimiterDropDown
            app.DelimiterDropDown = uidropdown(app.Panel);
            app.DelimiterDropDown.Items = {'Tab', 'Comma (,)', 'Semicolon (;)', 'Space', 'Bar (|)'};
            app.DelimiterDropDown.ItemsData = [1 2 3 4 5];
            app.DelimiterDropDown.ValueChangedFcn = createCallbackFcn(app, @HeaderlinesSpinnerValueChanged, true);
            app.DelimiterDropDown.Position = [145 191 100 22];
            app.DelimiterDropDown.Value = 1;

            % Create TimeunitDropDownLabel
            app.TimeunitDropDownLabel = uilabel(app.Panel);
            app.TimeunitDropDownLabel.HorizontalAlignment = 'right';
            app.TimeunitDropDownLabel.Position = [311 212 54 22];
            app.TimeunitDropDownLabel.Text = 'Time unit';

            % Create TimeunitDropDown
            app.TimeunitDropDown = uidropdown(app.Panel);
            app.TimeunitDropDown.Items = {'h', 'min', 's', 'ms', 'µs', 'ns'};
            app.TimeunitDropDown.ItemsData = [-2 -1 0 1 2 3];
            app.TimeunitDropDown.ValueChangedFcn = createCallbackFcn(app, @HeaderlinesSpinnerValueChanged, true);
            app.TimeunitDropDown.Position = [288 191 100 22];
            app.TimeunitDropDown.Value = 1;

            % Create ConvertfilesButton
            app.ConvertfilesButton = uibutton(app.Panel, 'push');
            app.ConvertfilesButton.ButtonPushedFcn = createCallbackFcn(app, @ConvertfilesButtonPushed, true);
            app.ConvertfilesButton.Enable = 'off';
            app.ConvertfilesButton.Position = [184 139 100 23];
            app.ConvertfilesButton.Text = 'Convert files';

            % Create HeaderlinesSpinnerLabel
            app.HeaderlinesSpinnerLabel = uilabel(app.Panel);
            app.HeaderlinesSpinnerLabel.HorizontalAlignment = 'right';
            app.HeaderlinesSpinnerLabel.Position = [38 212 72 22];
            app.HeaderlinesSpinnerLabel.Text = 'Header lines';

            % Create HeaderlinesSpinner
            app.HeaderlinesSpinner = uispinner(app.Panel);
            app.HeaderlinesSpinner.Limits = [0 Inf];
            app.HeaderlinesSpinner.ValueDisplayFormat = '%.0f';
            app.HeaderlinesSpinner.ValueChangedFcn = createCallbackFcn(app, @HeaderlinesSpinnerValueChanged, true);
            app.HeaderlinesSpinner.Editable = 'off';
            app.HeaderlinesSpinner.Position = [25 191 100 22];

            % Create PostfixEditFieldLabel
            app.PostfixEditFieldLabel = uilabel(app.Panel);
            app.PostfixEditFieldLabel.HorizontalAlignment = 'right';
            app.PostfixEditFieldLabel.Position = [17 139 41 22];
            app.PostfixEditFieldLabel.Text = 'Postfix';

            % Create PostfixEditField
            app.PostfixEditField = uieditfield(app.Panel, 'text');
            app.PostfixEditField.InputType = 'alphanumerics';
            app.PostfixEditField.ValueChangedFcn = createCallbackFcn(app, @HeaderlinesSpinnerValueChanged, true);
            app.PostfixEditField.Position = [73 139 100 22];

            % Create DecimalseparatorDropDownLabel
            app.DecimalseparatorDropDownLabel = uilabel(app.Panel);
            app.DecimalseparatorDropDownLabel.HorizontalAlignment = 'right';
            app.DecimalseparatorDropDownLabel.Position = [564 212 102 22];
            app.DecimalseparatorDropDownLabel.Text = 'Decimal separator';

            % Create DecimalseparatorDropDown
            app.DecimalseparatorDropDown = uidropdown(app.Panel);
            app.DecimalseparatorDropDown.Items = {'.', ','};
            app.DecimalseparatorDropDown.ItemsData = [1 2];
            app.DecimalseparatorDropDown.ValueChangedFcn = createCallbackFcn(app, @HeaderlinesSpinnerValueChanged, true);
            app.DecimalseparatorDropDown.FontWeight = 'bold';
            app.DecimalseparatorDropDown.Position = [600 191 39 22];
            app.DecimalseparatorDropDown.Value = 1;

            % Create ConvertionErrorTextArea
            app.ConvertionErrorTextArea = uitextarea(app.Panel);
            app.ConvertionErrorTextArea.Editable = 'off';
            app.ConvertionErrorTextArea.Visible = 'off';
            app.ConvertionErrorTextArea.Position = [298 11 261 130];

            % Create AnerroroccurredduringtheconversionofLabel
            app.AnerroroccurredduringtheconversionofLabel = uilabel(app.Panel);
            app.AnerroroccurredduringtheconversionofLabel.FontWeight = 'bold';
            app.AnerroroccurredduringtheconversionofLabel.FontColor = [0.7686 0.051 0.051];
            app.AnerroroccurredduringtheconversionofLabel.Visible = 'off';
            app.AnerroroccurredduringtheconversionofLabel.Position = [299 140 261 22];
            app.AnerroroccurredduringtheconversionofLabel.Text = 'An error occurred during the conversion of :';

            % Create valueLabel
            app.valueLabel = uilabel(app.Panel);
            app.valueLabel.Position = [510 192 42 22];
            app.valueLabel.Text = '* value';

            % Create ConvertUserEditField
            app.ConvertUserEditField = uieditfield(app.Panel, 'numeric');
            app.ConvertUserEditField.ValueChangedFcn = createCallbackFcn(app, @HeaderlinesSpinnerValueChanged, true);
            app.ConvertUserEditField.Visible = 'off';
            app.ConvertUserEditField.Position = [407 166 97 22];
            app.ConvertUserEditField.Value = 1;

            % Create ConvertamplitudetoDropDownLabel
            app.ConvertamplitudetoDropDownLabel = uilabel(app.Panel);
            app.ConvertamplitudetoDropDownLabel.HorizontalAlignment = 'right';
            app.ConvertamplitudetoDropDownLabel.Position = [407 212 122 22];
            app.ConvertamplitudetoDropDownLabel.Text = 'Convert  amplitude to ';

            % Create ConvertamplitudetoDropDown
            app.ConvertamplitudetoDropDown = uidropdown(app.Panel);
            app.ConvertamplitudetoDropDown.Items = {'10^-12 (T)', '10^-9 (G)', '10^-6 (M)', '10^-3 (k)', '1 (none)', '10^1 (d)', '10^2 (c)', '10^3 (m)', '10^6 (µ)', '10^9 (n)', '10^12 (p)', 'user input'};
            app.ConvertamplitudetoDropDown.ItemsData = [-12 -9 -6 -3 0 1 2 3 6 9 12 50];
            app.ConvertamplitudetoDropDown.ValueChangedFcn = createCallbackFcn(app, @ConvertamplitudetoDropDownValueChanged, true);
            app.ConvertamplitudetoDropDown.Position = [405 191 100 22];
            app.ConvertamplitudetoDropDown.Value = 0;

            % Create DoneLabel
            app.DoneLabel = uilabel(app.Panel);
            app.DoneLabel.BackgroundColor = [0.3922 0.8314 0.0745];
            app.DoneLabel.HorizontalAlignment = 'center';
            app.DoneLabel.Visible = 'off';
            app.DoneLabel.Position = [184 112 100 22];
            app.DoneLabel.Text = 'Done';

            % Create TextArea
            app.TextArea = uitextarea(app.Panel);
            app.TextArea.Editable = 'off';
            app.TextArea.BackgroundColor = [0.902 0.902 0.902];
            app.TextArea.Position = [13 11 252 41];
            app.TextArea.Value = {'(c) Michael Rabenstein'; 'Licensed under the BSD-3-Clause license'};

            % Show the figure after all components are created
            app.UIFigure.Visible = 'on';
        end
    end

    % App creation and deletion
    methods (Access = public)

        % Construct app
        function app = CSV_Converter_Episodic_ABF_analysis

            runningApp = getRunningApp(app);

            % Check for running singleton app
            if isempty(runningApp)

                % Create UIFigure and components
                createComponents(app)

                % Register the app with App Designer
                registerApp(app, app.UIFigure)

                % Execute the startup function
                runStartupFcn(app, @startupFcn)
            else

                % Focus the running singleton app
                figure(runningApp.UIFigure)

                app = runningApp;
            end

            if nargout == 0
                clear app
            end
        end

        % Code that executes before app deletion
        function delete(app)

            % Delete UIFigure when app is deleted
            delete(app.UIFigure)
        end
    end
end