function addsample(tsin,varargin)
%ADDSAMPLE  Add a single sample to a timeseries object
%
%   ADDSAMPLE(TS,S) adds a new sample S to the timeseries object TS. S is a
%   structure whose field names and values are the variable names and
%   values. ADDSAMPLE(TS, 'field1', VALUES1, 'field2', VALUES2,...) acts
%   the same by specifying the new sample as a collection of variable
%   name/value pairs. In both cases, the orders of the fields can be
%   arbitrarily selected. 
%
%   ADDSAMPLE(TS, 'field1', VALUES1, 'field2', VALUES2) requires that one
%   of the fields should be 'Time' and the other field should be the name
%   of the data variable whose default value is 'Data'. The VALUE of the time
%   term should be a real number. The size of the VALUE term corresponding
%   to the data variable should have the same size as an existing sample in
%   TS, given that TS is not empty. The following two commands will
%   generate the same result.  
%       
%       ts.addsample(struct('Data',3.2,'Time',3));
%       ts.addsample('Time',3,'Data',3.2);
%
%   ADDSAMPLE(TS, 'field1', VALUES1, 'field2', VALUES2, 'field3', VALUES3) 
%   includes an extra parameter which should be either 'Quality' or
%   'OverwriteFlag'. The VALUE term corresponding to the 'Quality' field
%   should be an integer. The VALUE corresponding to the 'OverwriteFlag'
%   field should be boolean. When the time of the new sample already exists
%   in TS, the old sample will be overwritten by the new sample when
%   OverwriteFlag is set TRUE. Otherwise, an error message will be
%   generated. For example  
%       
%       ts.addsample(struct('Data',3.2,'Time',3,'Quality',1));
%       ts.addsample('Data',3.2,'OverwriteFlag',true,'Time',3);
%
%   ADDSAMPLE(TS, 'field1', VALUES1, 'field2', VALUES2, 'field3', VALUES3,
%   'field4', VALUES4) includes two extra parameters: both 'Quality' and
%   'OverwriteFlag'.
%
%   See also TSDATA.TIMESERIES/TIMESERIES, TSDATA.TIMESERIES/DELSAMPLE

% Author(s): Rong Chen
% Revised:
% Copyright 1986-2004 The MathWorks, Inc.
% $Revision: 1.1.6.1 $ $Date: 2004/12/26 21:34:55 $

% -----------------------------------------------------------------------
% initialization: get index for both the grid variable (time) and dependent
% variales (data and quality).
% -----------------------------------------------------------------------
vars = cell2mat(get(tsin.Data_,{'Variable'}));
names=get(vars,{'Name'});
[dataName,idxData] = setdiff(names,{'Time';'Quality'});
if isempty(idxData)
    error('timeseries:addsample',...
     'there is no data variable defined in the timeseries.  Rebuild the timeseries');
end
[junk,idxTime]=intersect(names,'Time');
[junk,idxQuality]=intersect(names,'Quality');

% -----------------------------------------------------------------------
% Parse inputs
% -----------------------------------------------------------------------
if nargin>1
    try
        s = struct(varargin{:});
    catch
        error('timeseries:addsample',...
            'Invalid list of variable name/value pairs.')
    end
else
    s = varargin{1};
    if ~isa(s,'struct') || ~isscalar(s)
        error('timeseries:addsample',...
            'New sample must be specified as a scalar structure.')
    end
end
try
    data = getfield(s,dataName{:});
catch
    error('timeseries:addsample',...
        'Undefined value for variable: %s',dataName{:})
end
try
    time = s.('Time');
catch
    error('timeseries:addsample',...
        'Undefined value for variable: %s','Time')
end
if length(fieldnames(s))>2
    try
        quality = s.('Quality');
    catch
        quality=[];
    end
    try
        overwriteFlag = s.('OverwriteFlag');
    catch
        overwriteFlag=[];
    end
else
    quality=[];
    overwriteFlag=[];
end

% -----------------------------------------------------------------------
% stage 1: varify all the input information in the correct format
% -----------------------------------------------------------------------
% 1. make sure the data variable is good    
[data, datasrc] = localParseData(data);
% 2. make sure the time variable is good
time=localParseTime(tsin,time);
% 3. make sure the quality variable is good if any
quality=localParseQuality(tsin,idxTime,idxQuality,quality);
% 4. get overwrite flag if any
overwriteFlag=localParseOverwrite(tsin,overwriteFlag);

% -----------------------------------------------------------------------
% stage 2: data manipulation
% if the original timeseries is non-empty, check whether the new sample
% size equals the timeseries sample size. if the original timeseries is
% empty, use the new sample size for the timeseries.
% -----------------------------------------------------------------------
SampleSize=tsin.Data_(idxData).SampleSize;
% the original timeseries is non-empty
if ~isempty(SampleSize)
    % check if the input data size is consistent with the timeseries
    if ~all(size(data)==SampleSize)
        % deal with the vector case since in the hds object all the vectors
        % are saved as column vectors (SampleSize is always n-by-1)
        if ~(isvector(data) && all(size(data')==SampleSize))
            errstr = sprintf('%s: %s','The size of the data sample should be',num2str(SampleSize));
            error('timeseries:addsample',errstr);
        end
        data=data';
    end
    % get the timestamp characteristics of the new sample
    [isExisted,idx]=localFindTime(tsin,time);
    % check if it is a new time stamp
    if isExisted && ~overwriteFlag
        % it is an old time stamp but no overwrite is permitted
        error('timeseries:addsample',...
            'the time already exists.  To overwrite it, set the overwrite flag true');
    else
        % otherwise, add data
        localAddData(tsin,false,data,time,quality,isExisted,idx,idxData,idxTime,idxQuality);
    end
% the original timeseries is empty
else
    % if the new sample is a row vector, treat its time index along the
    % colomn axis, otherwise, treat the time index along the row axis.
    if isvector(data) 
        if size(data,1)>1
            %localAddData(tsin,true,data,time,quality,false,[],idxData,idxTime,idxQuality);
            tsin.init(data,{time});
            if ~isempty(quality)
                tsin.quality=quality;
            end
        else
            % NOTE: have to set GridFirst in two place since in ths special
            % case tsin.Data_(idxData).GridFirst is not automatically updated.
            tsin.DataInfo.GridFirst = false;
            tsin.Data_(idxData).GridFirst = false;
            %localAddData(tsin,true,data',time,quality,false,[],idxData,idxTime,idxQuality);
            tsin.init(data',{time});
            if ~isempty(quality)
                tsin.quality=quality;
            end
        end
    else
        tsin.init(data,{time});
        if ~isempty(quality)
            tsin.quality=quality;
        end
    end
end
% end of function


% -----------------------------------------------------------------------
% subroutine 1: varify the input information : data
% Data should represent the ordinate data.
% -----------------------------------------------------------------------
function [data, datasrc] = localParseData(datain)

data = [];
datasrc = [];
if isa(datain,'hds.ArrayContainer')
    datasrc = datain;
elseif (isnumeric(datain) && ~isempty(datain)) || isa(datain,'tsdata.ArrayAdaptor')
    data = datain;
else
    error('timeseries:addsample',...
        'The first argument must represent the ordinate data')
end
if isempty(data)
    error('timeseries:addsample',...
     'data can not be empty');
end    


% -----------------------------------------------------------------------
% subroutine 2: varify the input information : time
% Time: sample time which should be a non-empty real number.
% -----------------------------------------------------------------------
function time=localParseTime(tsin,timein)

% timein is a single time point
if ischar(timein)
    if tsin.timeInfo.Length==0
        % first time point
        time=timein;
    else
        % get relative time value
        [time,dummy]=tsAnalyzeAbsTime(timein,tsin.Timeinfo.Units,tsin.Timeinfo.Startdate);
    end
elseif isnumeric(timein)
    if isscalar(timein)
        time = timein;        
    else
        error('timeseries:addsample',...
        'time must be a scalor');
    end
else
    error('timeseries:addsample',...
        'invalid time format');
end



% -----------------------------------------------------------------------
% subroutine 3: varify the input information : quality
% varargin(1): sample quality (optional) which should be an integer. if
% the timeseries has empty (non-empty) quality value, the quality value
% should also be empty (non-empty). Otherwise, an error message is
% generated. 
% -----------------------------------------------------------------------
function quality=localParseQuality(tsin,idxTime,idxQuality,qualityin)

if ~isempty(qualityin)
    if ~isscalar(qualityin)
        error('timeseries:addsample',...
        'quality value must be a scalor.');
    end
    if ~isnumeric(qualityin) || ~isequal(round(qualityin),qualityin)
        error('timeseries:addsample',...
         'quality must be an integer value.');
    end
    if isempty(tsin.Data_(idxQuality).data) && ~(tsin.Grid_.Length==0)
        error('timeseries:addsample',...
         'quality value should be empty for this timeseries.');
    else
        quality=qualityin;
    end
else
    if isempty(tsin.Data_(idxQuality).data)
        quality=[];
    else
        error('timeseries:addsample',...
         'quality can not empty for this timeseries.');
    end
end


% -----------------------------------------------------------------------
% subroutine 4: varify the input information : overwrite flag
% varargin(2): a boolean varaible (optional). When the timestamp for the
% sample to be added already exists in the timeseries, if varargin(2)
% is set to be TRUE, the old sample will be overwritten by the new sample.
% Otherwise, an error message will be generated.
% -----------------------------------------------------------------------
function overwriteFlag=localParseOverwrite(tsin,overwriteFlagin)

if ~isempty(overwriteFlagin)
    if ~isscalar(overwriteFlagin)
        error('timeseries:addsample',...
        'overwrite must be a scalor');
    end
    if ~islogical(overwriteFlagin)
        error('timeseries:addsample',...
         'overwrite must be a boolean value');
    end
    overwriteFlag=overwriteFlagin;
else
    % default
    overwriteFlag=false;
end


% -----------------------------------------------------------------------
% subroutine 5: get the time stamp information for the new sample
% output argument 1: a boolean varaible. TRUE means the same timestamp
% already exists which means an updating case, FALSE means the timestamp i
% a new one, which implies either an appending case or an inserting case.
% output argument 2: an integer. If it is an updating case, it stores the
% index of the timestamp in the timeseries. If it is an appending case, its
% value is Inf.  If it is an inserting case, its value is the index of the
% closest timestap which is larger than the new timestamp.
% NOTE: this function is only used when the original timeseries is not
% empty.
% -----------------------------------------------------------------------
function [isExisted,idx]=localFindTime(tsin,time)

isExisted=[];
idx=[];
if isfinite(tsin.timeinfo.increment)
    % the original tmeseries is uniformly sampled
    if time>=tsin.timeinfo.start && time<=tsin.timeinfo.end
        % timestamp is within the time range of the original timeseries
        if isequal((time-tsin.timeinfo.start)/tsin.timeinfo.increment,round((time-tsin.timeinfo.start)/tsin.timeinfo.increment))
            % timestamp already exists in a uniformly sampled timeseries,
            % return the index
            isExisted=true;
            idx=(time-tsin.timeinfo.start)/tsin.timeinfo.increment+1;
        else
            % timestamp does not exist in a uniformly sampled timeseries,
            % return the index of the timestamp immediately larger than the
            % new timestamp
            isExisted=false;
            idx=ceil((time-tsin.timeinfo.start)/tsin.timeinfo.increment)+1;
        end            
    else
        % timestamp is beyond the time range of the original timeseries
        isExisted=false;
        if time<tsin.timeinfo.start
            % return 1 if the timestamp is samller than the first timestamp
            % in the timeseries.
            idx=1;
        else
            % return Inf if the timestamp is larger than the last timestamp
            % in the timeseries.
            idx=Inf;
        end
    end
else
    % the original tmeseries is non-uniformly sampled
    if time>=tsin.timeinfo.start && time<=tsin.time(end)
        % timestamp is within the time range of the original timeseries
        originalTime=tsin.time;
        idx=find(originalTime==time);
        if isempty(idx)
            % timestamp does not exist in the timeseries, return the index
            % of the timestamp immediately larger than the new timestamp
            isExisted=false;
            idx=find(originalTime>time,1);
        else
            % timestamp already exists in the timeseries, return the index.
            isExisted=true;
        end
    else
        % timestamp is beyond the time range of the original timeseries
        isExisted=false;
        if time<tsin.timeinfo.start
            % return 1 if the timestamp is samller than the first timestamp
            % in the timeseries.
            idx=1;
        else
            % return Inf if the timestamp is larger than the last timestamp
            % in the timeseries.
            idx=Inf;
        end
    end
end


% -----------------------------------------------------------------------
% subroutine 7: append/insert/update a sample into the timeseries
% if the original timeseries is NOT uniformly sampled, add the sample
% first and changes will automatically happen if the timeseries becomes
% a uniformly sampled one after the addition operation.  vice versa
% -----------------------------------------------------------------------
function localAddData(tsin,emptyTS,data,time,quality,isExisted,idx,idxData,idxTime,idxQuality)

% empty case or append case
if emptyTS || (~isExisted && ~isfinite(idx))
    % get old grid size
    oldGridSize = tsin.Grid_.Length;
    % Update grid dimension length
    tsin.Grid_.Length = tsin.Grid_.Length + 1;
    % Update data for grid variables
    c = tsin.Data_(idxTime);
    c.SampleSize = [1 1];  % in case grid dimension is empty
    % REVISIT: should automatically convert to cell array when fails
    if isfinite(c.Metadata.Increment) && time-c.Metadata.End==c.Metadata.Increment
        % uniformly before and uniformly after
        c.Metadata.setlength(c.Metadata.getlength+1);
    else
        % otherwise
        c.setArray([c.getArray ; time]);
    end
    % Built index locating old grid inside new grid
    idx = cell(1,length(oldGridSize));
    for ct=1:length(oldGridSize)
        idx{ct} = 1:oldGridSize(ct);
    end
    newGridSize = [tsin.Grid_.Length];
    % for Data array
    c = tsin.Data_(idxData);
    %if isempty(c.data)
    %    c.SampleSize=size(data);
    %end
    c.setSlice({newGridSize},{data},newGridSize);
    % for Quality array
    c = tsin.Data_(idxQuality);
    if ~isempty(quality)
        tsin.Data_(idxQuality).SampleSize=size(quality);
        temp=quality;
        if ~isnan(quality)
            quality=int8(quality);
            if abs(temp-double(quality))>eps(255)
                error('timeseries:addsample',...
                    'Quality codes must all be in the range -128 to 127')
            end
        end
        tsin.Data_(idxQuality).data(tsin.Grid_.Length,1)=quality;
    end
% update case
elseif isExisted
    % replace code using getsample/setsample functions from hds
    
    %sample=tsin.getsample(idx);
    %sample.Data=data;
    %sample.Time=time;
    %sample.Quality=quality;
    %tsin.setsample(idx,sample);
    
    % with code directly access dataset storage space
    % special treatment to data in scalor or vector format
    %is = repmat({':'},[1 length(tsin.Data_(idxData).SampleSize)]);
    %if tsin.DataInfo.GridFirst
    %    tempIndex=[{idx} is];
    %else
    %    tempIndex=[is {idx}];
    %end
    %tsin.Data_(idxData).data(tempIndex{:})=data;
    c = tsin.Data_(idxData);
    c.setSlice({idx},{data},tsin.Grid_.Length);
    
    if ~isempty(quality)
        temp=quality;
        if ~isnan(quality)
            quality=int8(quality);
            if abs(temp-double(quality))>eps(255)
                error('timeseries:addsample',...
                    'Quality codes must all be in the range -128 to 127')
            end
        end
        tsin.Data_(idxQuality).data(idx)=quality;
    end
% insert case
else
    % replace code using getsample/setsample functions from hds
    
    %sample_front=tsin.getsample(idx);
    %sample=sample_front;
    %sample.Data=data;
    %sample.Time=time;
    %sample.Quality=quality;
    %tsin.setsample(idx,sample);
    %for i=idx+1:length(tsin.time)
    %    sample_this=tsin.getsample(i);
    %    tsin.setsample(i,sample_front);
    %    sample_front=sample_this;
    %end
    %localAddGridTime(tsin,sample_front.Time,idxData,idxTime,idxQuality);
    %tsin.setsample(length(tsin.time),sample_front);
    
    % with code directly access dataset storage space
    % generate is size
    is = repmat({':'},[1 length(tsin.Data_(idxData).SampleSize)]);
    if all(tsin.Data_(idxData).SampleSize~=1)
        % special treatment to data in matrix format
        if tsin.DataInfo.GridFirst
            tempIndex=[{1} is];
        else
            tempIndex=[is {1}];
        end
        temp(tempIndex{:})=data;
        data=temp;
    end
       
    % deal with time
    if isnan(tsin.Data_(idxTime).Metadata.Increment)
        tempTime=tsin.Data_(idxTime).data;
    else
        tempTime=[tsin.Data_(idxTime).Metadata.Start:tsin.Data_(idxTime).Metadata.Increment:tsin.Data_(idxTime).Metadata.End]';
    end
    if idx>1
        tempTime=[tempTime(1:idx-1);time;tempTime(idx:end)];
    else
        tempTime=[time;tempTime];
    end
    dt = diff(tempTime);
    % BE CAREFUL, the order of the following code in the if-then should not
    % be changed
    if (max(dt)-min(dt))/min(dt)<1e-6
        % uniformly sampled
        tsin.Data_(idxTime).Metadata.Increment = dt(1);
        tsin.Data_(idxTime).Metadata.Start = tempTime(1);
        tsin.Data_(idxTime).Metadata.setlength(length(tempTime));
        tsin.Data_(idxTime).data=[];
        tsin.Grid_.Length = tsin.Grid_.Length + 1;
    else
        % non-uniformly sampled
        tsin.Data_(idxTime).Metadata.Increment = 0;
        tsin.Data_(idxTime).Metadata.Start = tempTime(1);
        tsin.Data_(idxTime).Metadata.Increment = NaN;
        tsin.Data_(idxTime).Metadata.setlength(length(tempTime));
        tsin.Data_(idxTime).data=tempTime;
        tsin.Grid_.Length = tsin.Grid_.Length + 1;
    end

    % deal with data and quality
    tempData=tsin.Data_(idxData).data;
    tempQuality=tsin.Data_(idxQuality).data;
    % BE CAREFUL, the order of the quality and data calculation should not
    % be changed
    if idx>1
        if tsin.DataInfo.GridFirst
            tempIndex1=[{1:idx-1} is];
            tempIndex2=[{idx:size(tempData,1)} is];
            if ~isempty(quality)
                tempQuality=[tempQuality(1:idx-1);quality;tempQuality(idx:size(tempData,1))];
            end
            if isvector(data)
                data=data';
            end
            tempData=[tempData(tempIndex1{:});data;tempData(tempIndex2{:})];
        else
            tempIndex1=[is {1:idx-1}];
            tempIndex2=[is {idx:size(tempData,ndims(tempData))}];
            if ~isempty(quality)
                tempQuality=[tempQuality(1:idx-1);quality;tempQuality(idx:size(tempData,ndims(tempData)))];
            end
            tempData=cat(max(ndims(tempData),3),tempData(tempIndex1{:}),data,tempData(tempIndex2{:}));
        end
    else
        if tsin.DataInfo.GridFirst
            tempIndex2=[{1:size(tempData,1)} is];
            if ~isempty(quality)
                tempQuality=[quality;tempQuality(1:size(tempData,1))];
            end
            if isvector(data)
                data=data';
            end
            tempData=[data;tempData(tempIndex2{:})];
        else
            tempIndex2=[is {1:size(tempData,ndims(tempData))}];
            if ~isempty(quality)
                tempQuality=[quality;tempQuality(1:size(tempData,ndims(tempData)))];
            end
            tempData=cat(max(ndims(tempData),3),data,tempData(tempIndex2{:}));
        end
    end
    % save data and quality
    tsin.Data_(idxData).setArray(tempData);
    tsin.Data_(idxQuality).setArray(tempQuality);
end

    


