function sw0Reader(inputFileName, NameValueArgs)
%sw0Reader Convert .sw0 file to .mat or .csv file 
%   This function reads binary sw0 files produced by HSpice or Finesim. It
%   outputs the data as a MAT or CSV file. This function does not support
%   AC or transient simulation data files (.ac0, .tr0).
%
%   Usage:
%       sw0Reader(inputFileName)
%       sw0Reader(inputFileName, Name, Value)
%       sw0Reader(inputFileName, Name=Value)
%
%   Arguments:
%       inputFileName   Text: Name of the .sw0 file to convert.
%
%   Name-Value Pair Arguments:
%       InputPath       pwd (default) | Text: Location where sw0FileName is
%                       placed.
%       OutputType      'csv' (default) | 'mat': The type of output file to
%                       generate.
%       OutputPath      pwd (default) | Text scalar: Location where
%                       OutputFileName will be saved.
%       OutputFileName  inputFileName (default) | Text: Name for output
%                       file. Any occurrances of * in the file name will be
%                       replaced with the file's number. If multiple output
%                       files must be written and the file name doesn't
%                       contain *, the file number will be appended to the
%                       provided file name.
%
%   Outputs:
%       This function creates a file of the specified type in the specified
%       location to hold the data contained in the input file.
%
%   See Also: ac0Reader, tr0Reader

%   Copyright 2022-2023 The MathWorks, Inc.

arguments
    inputFileName {mustBeTextScalar, mustBeNonzeroLengthText};
    NameValueArgs.InputPath {mustBeTextScalar} = "";
    NameValueArgs.OutputType {validatestring(NameValueArgs.OutputType, ["csv", "mat"], "sw0Reader", "OutputType")} ='csv';
    NameValueArgs.OutputPath {mustBeTextScalar} = pwd;
    NameValueArgs.OutputFileName {mustBeTextScalar, mustBeNonzeroLengthText} = inputFileName;
    NameValueArgs.Sw0FilePath {mustBeTextScalar} = ""; % Legacy InputFilePath
end
% Compatibility with old (Sw0FilePath) argument name
if ~strcmp(NameValueArgs.Sw0FilePath, "") && strcmp(NameValueArgs.InputPath, "")
    NameValueArgs.InputPath = NameValueArgs.Sw0FilePath;
elseif strcmp(NameValueArgs.InputPath, "") % Apply default value
    NameValueArgs.InputPath = pwd;
end

%Checking for errors in input NameValueArgs and returning fileNames.
[inputFileName, NameValueArgs.OutputFileName] = checkInputParams(inputFileName, NameValueArgs);

sw0File = fullfile(NameValueArgs.InputPath,inputFileName);
outputFile = fullfile(NameValueArgs.OutputPath,NameValueArgs.OutputFileName);

% Get Endianness of file (and detect ASCII files)
try
    isLittleEndian = msblks.utilities.tr0.getEndianness(sw0File);
catch ME
    throw(ME);
end

if isLittleEndian
    [fileID, errorMessage] = fopen(sw0File, 'r', 'l');
else
    [fileID, errorMessage] = fopen(sw0File, 'r', 'b');
end
if fileID == -1
    throw(MException("msblks:msblksMessages:Tr0FileError", errorMessage));
end

%HEADER SECTION
inHeaderSection = true;
curBlock = 0;
curNode = 0;

%Additional Structures
has2013 = false;
has2001 = false;

staticNodes = 0;

while inHeaderSection
    %Preamble check
    [preamble, count] = fread(fileID, 4, 'uint32');
    try
        preambleCheck(preamble, count, sw0File, ftell(fileID));
    catch ME
        fclose(fileID);
        throw(ME);
    end
    [blockCharArray, count] = fread(fileID, preamble(4), '*char');
    if count ~= preamble(4)
        byte = ftell(fileID);
        fclose(fileID);
        throw(MException(message("msblks:msblksMessages:Tr0UnexpectedEOF", sw0File, byte)));
    end
    blockStr = convertCharsToStrings(blockCharArray);
    blockLen = preamble(4);

    %Postamble check
    [postamble, count] = fread(fileID, 1, 'uint32');
    try
        postambleCheck(preamble, count, postamble, sw0File, ftell(fileID));
    catch ME
        fclose(fileID);
        throw(ME);
    end
    curBlock = curBlock + 1;

    %double Precision
    if(curBlock==1)
        checkString = convertCharsToStrings(blockCharArray(1:30));
        has2013 = contains(checkString, '2013');
        has2001 = contains(checkString, '2001');
    end

    %Get VOLTS
    strIdx = 1;
    if curNode == 0
        [csvRow, strIdx, curNode] = getCsvRow(blockStr, curNode);
    end

    % Parse data from the remaining block string
    if (curNode > 0) && (strIdx > 0)
        [parsedStr, atEnd, curNode, staticNodes, nodeType] = parseBlockStr(blockCharArray, strIdx, blockLen, curNode);
        inHeaderSection = ~atEnd;
        csvRow = [csvRow, ',', parsedStr];
    end
end

if curNode == 0 || curNode == 1
    fclose(fileID);
    throw(MException(message("msblks:msblksMessages:Tr0EmptyHeaderSection", sw0File)));
end

% split the columns headers string into columns
columnHeaders = split(csvRow, ',')';
isCSV = strcmp(NameValueArgs.OutputType, 'csv');
try
    writeMAT(columnHeaders, sw0File, fileID, curNode, has2013, has2001, staticNodes, nodeType, outputFile, isCSV);
catch ME
    fclose(fileID);
    throw(ME);
end
fclose(fileID);
end

function [sw0FileName, outputFileName] = checkInputParams(sw0FileName, NameValueArgs)
%checkInputParams Validate parameters

[~, ~, ext] = fileparts(sw0FileName);
% check if the file has ac0 extension
% if file has no extention then add ac0 extention to it
if strcmp(ext, "")
    sw0FileName = sw0FileName + ".sw0";
elseif ~isempty(regexp(ext, "^\.((tr)|(ac))\d+$", "match")) % Did you mean tr0Reader() or sw0Reader()?
    ME = MException(message("msblks:msblksMessages:Tr0InvlaidFileType", ext, "sw"));
    prefix = regexp(ext, "((tr)|(ac))", "match", "once");
    ME = ME.addCorrection(matlab.lang.correction.ReplaceIdentifierCorrection("sw0Reader", strcat(prefix, "0Reader")));
    throwAsCaller(ME);
elseif isempty(regexp(ext, "^\.sw\d+$", "match"))
    ME = MException(message("msblks:msblksMessages:Tr0InvlaidFileType", ext, "sw"));
    throwAsCaller(ME);
end

% check if file path exists or not
% check if file exists in the given file path
if exist(NameValueArgs.InputPath, "dir") == 0 || ...
        ~exist(fullfile(NameValueArgs.InputPath, sw0FileName), "file")
    ME = MException(message("msblks:msblksMessages:Tr0CouldNotFindFile", sw0FileName, NameValueArgs.InputPath));
    throwAsCaller(ME);
end

% Replace empty path with pwd
if(isempty(NameValueArgs.OutputPath))
    NameValueArgs.OutputPath = pwd;
end

% check if output path exists or not
if exist(NameValueArgs.OutputPath, "dir") == 0
    ME = MException(message("msblks:msblksMessages:Tr0CouldNotFindFile", sw0FileName, NameValueArgs.OutputPath));
    throwAsCaller(ME);
end

% if not output type is provided add default output type
[~, ~, ext] = fileparts(NameValueArgs.OutputFileName);
if ~contains(ext, NameValueArgs.OutputType)
    outputFileName = strcat(NameValueArgs.OutputFileName, ".", NameValueArgs.OutputType);
else
    outputFileName = NameValueArgs.OutputFileName;
end

% issue warning if output file already exists and will be overwritten
fullOutputFile = fullfile(NameValueArgs.OutputPath, outputFileName);
if isfile(fullOutputFile)
    warning(message("msblks:msblksMessages:Tr0FileAlreadyExists", fullOutputFile));
end
end

% Preamble checks
function preambleCheck(preamble, count, file, byte)
if count ~= 4
    ME = MException(message("msblks:msblksMessages:Tr0PreambleLength", file, byte, count));
    throwAsCaller(ME);
elseif preamble(1) ~= 4
    ME = MException(message("msblks:msblksMessages:Tr0PreambleLength", file, byte, preamble(4)));
    throwAsCaller(ME);
elseif preamble(4) > 16384
    ME = MException(message("msblks:msblksMessages:Tr0BlockSizeOverflow", file));
    throwAsCaller(ME);
end
end

% Postamble checks
function postambleCheck(preamble, count, postamble, file, byte)
if count ~= 1
    ME = MException(message("msblks:msblksMessages:Tr0UnexpectedEOF", file, byte));
    throwAsCaller(ME);
elseif postamble ~= preamble(4)
    ME = MException(message('msblks:msblksMessages:Tr0PreamblePostambleDiffSize', file, byte, postamble, preamble(4)));
    throwAsCaller(ME);
end
end

% find the 'VOLTS' string to start the header section.
function [str, idx, node] = getCsvRow(blockStr, ~) % replaced unused curNode with ~
strIdx = strfind(blockStr, 'VOLTS');
idx = strIdx;
if strIdx ~= 0
    %node = curNode + 1;
    node = 1;
    str = 'VOLTS';
    idx = idx + 5;
end
end

% The function get the header columns names in comma seperated string
function [parsedStr, reachedEnd, nodes, staticNodes, nodeNameType] = parseBlockStr(blockCharArray, strIdx, blockLen, curNode)
idx = strIdx;
tempStr = '';
reachedEnd = false;
parsedStr = '';
nodes = curNode;
staticNodes = 0;
nodeNameType=zeros(1000, 1);
is_x=0; % x axis node
is_i=1; % i( node
is_v=2; % v( node
is_u=3; % unknown node
is_vm=4; % vm( node
is_vp=5; % vp( node
is_static=6; % no "(", means that this value is held static. Its value
% is inserted once at the beginning of the data section, and
% not repeated on each row of the data. Code is change to
%
while(idx<=blockLen && ~reachedEnd)
    if(isempty(tempStr) && blockCharArray(idx)==' ')
        idx = idx+1;
    elseif(idx==blockLen)
        if(~isempty(tempStr) && ~strcmp(tempStr,'$&%#'))
            if(tempStr(end)~=')' && tempStr(2)=='(')
                tempStr = [tempStr,')'];
            end
            nodes = nodes+1;
            if(contains(tempStr,'i('))
                nodeNameType(nodes)=is_i;
            elseif(contains(tempStr,'v('))
                nodeNameType(nodes)=is_v;
            elseif(contains(tempStr,'vm('))
                nodeNameType(nodes)=is_vm;
            elseif(contains(tempStr,'vp('))
                nodeNameType(nodes)=is_vp;
            elseif(contains(tempStr,'('))
                nodeNameType(nodes)=is_u;
            else
                nodeNameType(nodes)=is_static;
                staticNodes = staticNodes+1;
            end
            if(isempty(parsedStr))
                parsedStr = tempStr;
            else
                parsedStr = [parsedStr,',',tempStr];
            end
            tempStr='';
        end
        tempStr='';
        reachedEnd = true;
    elseif(blockCharArray(idx)==' ')
        if(strcmp(tempStr,'$&%#'))
            reachedEnd = true;
            tempStr='';
        else
            nodes = nodes+1;
            if(strlength(tempStr)>=2 && tempStr(strlength(tempStr))~=')' && tempStr(2)=='(')
                tempStr = [tempStr,')'];
            end
            if(contains(tempStr,'i('))
                nodeNameType(nodes)=is_i;
            elseif(contains(tempStr,'v('))
                nodeNameType(nodes)=is_v;
            elseif(contains(tempStr,'vm('))
                nodeNameType(nodes)=is_vm;
            elseif(contains(tempStr,'vp('))
                nodeNameType(nodes)=is_vp;
            elseif(contains(tempStr,'('))
                nodeNameType(nodes)=is_u;
            else
                nodeNameType(nodes)=is_static;
                staticNodes = staticNodes+1;
            end
            if(isempty(parsedStr))
                parsedStr = tempStr;
            else
                parsedStr = [parsedStr,',',tempStr];
            end
            tempStr='';
        end
        idx = idx+1;
    else
        tempStr = [tempStr, blockCharArray(idx)];
        idx=idx+1;
    end
end
end

% This function writes the table data into CSV or MAT file depending on
% user input. It stores the give in the provided output path.
function writeMAT(columnHeaders, inputFile, fileID, curNode, has2013, has2001, staticNodes, nodetype, outputPath, isCSV)

% create table with 1 row and n columns, where n is the length of
% column headers array.
noOfColums = length(columnHeaders);
sz = [1, noOfColums];
varTypes = repmat("double", sz);
Table = table('Size', sz, 'VariableTypes', varTypes, 'VariableNames', columnHeaders);

% Get Data
csvRow = 1;
inDataSection = true;
curBlock = 0;
curField = 0;
cellArr = cell(1, noOfColums);
% getting all static nodes values
if staticNodes > 0
    staticValues = size(staticNodes, 1);
    staticCellArr = cell(1, staticNodes);
end

% reading data from a block
while inDataSection
    [preamble, count] = fread(fileID, 4, "uint32");
    if(count == 0)
        % End of file
        if(has2013)
            ME = MException(message("msblks:msblksMessages:Tr0UnexpectedEOF", inputFile, ftell(fileID)));
            throwAsCaller(ME);
        end
        %inDataSection = false;
        break
    end
    try
        preambleCheck(preamble, count);
    catch ME
        throwAsCaller(ME);
    end
    curBlock = curBlock + 1;
    blockSize = preamble(4) / 4;
    curWord = 0;

    % getting static nodes and their indexes
    if has2001 && csvRow == 1
        for i = 1:staticNodes
            [staticValues(i), count] = fread(fileID, 1, 'float64');
            if count ~= 1
                ME = MException(message("msblks:msblksMessages:Tr0UnexpectedEOF", inputFile, ftell(fileID)));
                throwAsCaller(ME);
            end
            curWord = curWord + 2;
        end
    end
    % reading data from a block
    while curWord < blockSize
        column = mod(curField, curNode) + 1;
        curField = curField + 1;
        try
            [curWord, data] = fillRowData(has2013, has2001, inputFile, fileID, curWord, column);
        catch ME
            throwAsCaller(ME);
        end
        cellArr{1, column} = data;
        if (column == 1) && (cellArr{1, 1} >= 1.e29) && (curWord == blockSize)
            inDataSection = false;
        end
        if column == curNode - staticNodes
            if staticNodes ~= 0
                static_j = 0;
                notStatic_j =0;
                for i = 1:curNode
                    if nodetype(i) == 6
                        static_j = static_j + 1;
                        staticCellArr{1, i} = staticValues(static_j);
                    else
                        notStatic_j = notStatic_j + 1;
                        staticCellArr{1, i} = cellArr{1, notStatic_j};
                    end
                end
                Table(csvRow, :) = staticCellArr;
                curField = curField + 1;
            else
                Table(csvRow, :) = cellArr;
            end
            csvRow = csvRow + 1;
        end
    end

    %postamble
    [postamble, count] = fread(fileID, 1, 'uint32');
    try
        postambleCheck(preamble, count, postamble, inputFile, ftell(fileID));
    catch ME
        throwAsCaller(ME);
    end
end

% write to given output file
if isCSV == false
    save(outputPath, 'Table');
else
    writetable(Table, outputPath);
end
end

% This function get the row data for the data section of the table
function [newIdx, data] = fillRowData(has2013, has2001, fileName, fileID, curWord, firstRow)
newIdx = curWord;
if firstRow == 1 % pretty sure this is actually checking for first column?
    if has2013 || has2001
        [r8, count]=fread(fileID, 1, 'float64');
        if count ~= 1
            error(message("msblks:msblksMessages:Tr0UnableToReadDPcolumn", fileName, strcat(getString(message("msblks:msblksMessages:DP")), " VOLTS")));
        end
        data = r8;
        newIdx = newIdx + 2;
    else
        [r4, count] = fread(fileID, 1, 'float32');
        if count ~= 1
            error(message("msblks:msblksMessages:Tr0UnableToReadDPcolumn", fileName, strcat(getString(message("msblks:msblksMessages:SP")), " VOLTS")));
        end
        data = r4;
        newIdx = newIdx + 1;
    end
else
    if has2001
        [r8, count] = fread(fileID, 1, 'float64');
        if count ~= 1
            error(message("msblks:msblksMessages:Tr0UnableToReadDPcolumn", fileName, strcat(getString(message("msblks:msblksMessages:DP")), " VOLTS")));
        end
        data = r8;
        newIdx = newIdx + 2;
    else
        [r4, count] = fread(fileID, 1, 'float32');
        if(count ~= 1)
            error(message("msblks:msblksMessages:Tr0UnableToReadDPcolumn", fileName, strcat(getString(message("msblks:msblksMessages:SP")), " VOLTS")));
        end
        data = r4;
        newIdx = newIdx + 1;
    end
end
end
