% Writing of datasets and variables to spreadsheet

% Copyright 2017-2019 The MathWorks, Inc.
classdef WriteTable < handle
    properties
        Filename (1,1) string;
        Sheet (1,1);
        Range (1,1) string;
        FilterColumns (1,1) logical = true;
        PreserveTolerance(1,1) logical = false;
    end

    properties (SetAccess = private)
        M table; % metadata
        D table; % data
        ds {mustBeScalarOrEmpty}; % Dataset, DatasetRef
        CurrentCol (1,1) double = 1;
        FcnCallCol (1,1) double = 0;
        CurrentTimeValues (:,1) double;
        TypesCol (:,1) xls.internal.MetadataTypes;
        SourceType (1,1) xls.internal.SourceTypes;
        SourceWriter (1,1) xls.internal.SourceWriter;
        SimulationWriter (1,1) xls.internal.SimulationWriter;
        Simulation (1,1) int8 {mustBeGreaterThanOrEqual(Simulation, 0), ...
            mustBeLessThanOrEqual(Simulation, 2)} = 0;
        Parameters (1,:) Simulink.Simulation.Variable;
        Tolerance (1,1) xls.internal.Tolerance;
    end

    properties (Hidden, Constant)
        TextType = 'string';
        DefaultCell = "a1";
        DefaultExt = ".xlsx";
        TextDelimitedExts = [".csv", ".txt", ".dat"];
        SpreadsheetExts = [".xls", ".xlsx", ".xlsm", ".xlsb"];
        TimeColumnName = "time";
        TColumnName = "t";
        UseExcel = false;
    end
    
    properties (Hidden)
        CachedTable;
        hasIntscnSync = false;
    end

    methods
        function this = WriteTable(varargin)
            import xls.internal.SourceTypes;
            if ~(ischar(varargin{1}) || isStringScalar(varargin{1}))
                varargin = [{'Dataset'}, varargin]; % backwards compatibility
            end
            stringClasses = ["char", "string"];
            p = inputParser;
            p.addParameter('Dataset',Simulink.SimulationData.Dataset.empty);
            p.addParameter('Filename',"",@(x)validateattributes(x,stringClasses,{'nonempty'}));
            p.addParameter('Sheet',"",@(x)validateattributes(x,[stringClasses,"numeric"],{'nonempty'}));
            p.addParameter('Range',"",@(x)validateattributes(x,stringClasses,{'nonempty'}));
            p.addParameter('FilterColumns',true,@(x)validateattributes(x,"logical",{'scalar'}));
            p.addParameter('SourceType',SourceTypes.Unset);
            p.addParameter('Simulation',0);
            p.addParameter('Parameters',Simulink.Simulation.Variable.empty);
            p.addParameter('Tolerance', xls.internal.Tolerance);
            p.addParameter('PreserveTolerance', false,@(x)validateattributes(x,"logical",{'scalar'}));
            p.parse(varargin{:});
            sheet = p.Results.Sheet;
            if ischar(p.Results.Sheet)
                sheet = string(p.Results.Sheet);
            end

            this.ds = p.Results.Dataset;
            this.Filename = string(p.Results.Filename);
            this.Sheet = sheet;
            this.Range = string(p.Results.Range);
            this.FilterColumns = p.Results.FilterColumns;
            this.SourceType = p.Results.SourceType;
            this.SourceWriter = xls.internal.SourceWriter(this.SourceType);
            this.Simulation = p.Results.Simulation;
            this.SimulationWriter = xls.internal.SimulationWriter(this.Simulation);
            this.Parameters = p.Results.Parameters;
            this.Tolerance = p.Results.Tolerance;
            this.PreserveTolerance = p.Results.PreserveTolerance;
            getFileName(this);
        end

        write(this);
        T = writeParameters(this);
        [M, D] = createTable(this);
        addColumnToTable(this, column);
        clearRange(this, workbook);
        T = getCurrentReadTable(this);
    end

    methods (Access = private)
        column = enforceRectangularData(this, tableName, column, fillValue);
        addTimeSeries(this, vValues, signalNames, elementName, parsedElem, isBus);
        addTimeColumn(this);
        addExtraHeaders(this, headers);
        addTimeColumnIfNeeded(this, parsedVar);
        resetTable(this);
        parseDataset(this, ds);
        headers = createColumnHeaders(this, parsedVar, dataValues, parsedElem);
        dataStart = getDataRangeStart(this, M);
        wtd = getWriteTableDimensions(this, dataValues, allDims);
        wtd = getNonFilteredDimensions(this, column, dims, writeDimension, isLastDim);
        stablePartition(this, pivotCol, pivotLoc);
        [M, D] = updateExistingSource(this);
        addParametersToTable(this, T);
        tol = getGlobalTolerance(this);
        tol = getToleranceFromCache(this, name, parsedElem, isBus);
    end

    methods (Static)
        column = extractDimension(dataValues, dims);
        bool = isValidExtension(filename);
        createEmptyFile(file, sheet);
        parsedVar = getParsedVar(var, name);
    end

    methods (Static, Access = private)
        dimensions = getAllDimensions(dataSize);
        data = getParsedVarData(parsedVar, fcn);
        [vTs, elementName] = parseElement(element, elementName, parsedElem);
        bool = nameNeedsEscaping(name);
    end
end

function mustBeScalarOrEmpty(property)
    if numel(property) > 1
        error(message('MATLAB:type:PropSetDimMismatchScalar'))
    end
end

function getFileName(this)
    if strlength(this.Filename) > 0
        [~,~,ext] = fileparts(this.Filename);
        if strlength(ext) == 0
            this.Filename = this.Filename + this.DefaultExt;
        end
    end
end
