% NCGEOVARIABLE Provide advanced access to variables and their related
% dimensions.
%
% NCGEOVARIABLE is used to retrieve data for a given variable as well as the
% variables associated coordinate dimensions.
%
% Methods:
%
% For more information on the methods use 'help' or 'doc'.  For example:
%   >> help ncgeovariable
%   >> doc ncgeovariable
%
% Example of use:
%  ds = ncgeodataset('http://dods.mbari.org/cgi-bin/nph-nc/data/ssdsdata/deployments/m1/200810/OS_M1_20081008_TS.nc');
%  vn = ds.variable('TEMP');    % returns an ncvariable
%  vg = ds.geovariable('TEMP'); % returns an ncgeovariable
%  tn = vn.data([1 1 1 1], [100 5 1 1]);
%  tg = vg.data(1:100,1:5,1,1);
%  % Look at properties
%  vg.name
%  vg.axes
%  vg.getaxesorder
%  vg.extent
%  datestr(vg.timeextent)
%  vg.grid_interop
%  tw = vg.timewindow([2008 10 10 0 0 0], [2008 20 11 0 0 0]);
%  datestr(tw.time)
%  vg.getlonname
%  vg.getlatname
%
%  vg.getxname
%  vg.getyname
%
% NCTOOLBOX (https://github.com/nctoolbox/nctoolbox
% https://github.com/nctoolbox/nctoolbox/wiki/ncgeovariable)
% See also ncvariable, ncgeodataset, ncgeodataset.geovariable
classdef ncgeovariable < ncvariable

    properties (SetAccess = private)
        %         dataset          % ncdataset instance.
    end

    properties (Dependent = true)
        %         name            % The string variable name that this object represents.
        %         attributes      % The attributes associated with this object.
        %         axes            % The coordiante vaaibles associated with this object.
    end

    properties (SetAccess = private, GetAccess = protected)
        %         variable        % ucar.nc2.Variable instance. Represents the data
        %         axesVariables    % ucar.nc2.Variable instance. Represents the data.

    end

    properties (SetAccess = private, GetAccess = private)
        %         variable        % ucar.nc2.Variable instance. Represents the data
        %         axesVariables    % ucar.nc2.Variable instance. Represents the data.
        axes_info % list of axes names and the dimensions in one cell dict
    end

    methods

        %%
        function obj = ncgeovariable(src, variableName, axesVariableNames)
            % NCGEOVARIABLE.NCGEOVARIABLE  Constructor.
            %
            % Use as:
            %    v = ncgeovariable(src, variableName, axesVariableNames)
            %
            % Preffered Usage:
            %    geo = ncgeodataset(...);
            %    v = geo.geovariable(variableName); % uses ncgeodataset.geovariable
            % Alternate Usage:
            %    geo = ncgeodataset(...);
            %    v = ncgeovariable(geo,variableName,geo.axes(variableName))
            obj = obj@ncvariable(src, variableName, axesVariableNames);

        end % ncgeovariable end

        %%
        function a = get.axes_info(src)
            switch length(src.size)
                case 1
                    a = fieldnames( src.grid_interop(1,1,1));
                    b = fieldnames( src.grid(1,1,1));
                case 2
                    a = fieldnames( src.grid_interop([1,1],[1,1],[1,1]));
                    b = fieldnames( src.grid([1,1],[1,1],[1,1]));
                case 3
                    a = fieldnames( src.grid_interop([1,1,1], [1,1,1], [1,1,1]));
                    b = fieldnames( src.grid([1,1,1], [1,1,1], [1,1,1]));
                case 4
                    a = fieldnames( src.grid_interop([1,1,1,1], [1,1,1,1], [1,1,1,1]));
                    b = fieldnames( src.grid([1,1,1,1], [1,1,1,1], [1,1,1,1]));
                case 5
                    a = fieldnames( src.grid_interop([1,1,1,1,1], [1,1,1,1,1], [1,1,1,1,1]));
                    b = fieldnames( src.grid([1,1,1,1,1], [1,1,1,1,1], [1,1,1,1,1]));
                case 6
                    a = fieldnames( src.grid_interop([1,1,1,1,1,1], [1,1,1,1,1,1], [1,1,1,1,1,1]));
                    b = fieldnames( src.grid([1,1,1,1,1,1], [1,1,1,1,1,1], [1,1,1,1,1,1]));
            end
            len = length(a);
            for i = 1:len
                a{i, 2} = src.dataset.size(b{i});
            end

        end

        function e = top_bot(src)
            % NCGEOVARIABLE.top_bot - Find top and bottom layers
            % Usage e = var.top_bot;
            % returns [top_index bottom_index]
            s = src.size;
            lens = length(s);
            first = ones(1,lens);
            if lens == 3
                s(2) = 1;
                s(3) = 1;
            elseif lens==4
                s(1) = 1;
                s(3) = 1;
                s(4) = 1;
            else
            end
            stride = first;
            %             switch length(s) % hopefully this speeds up the grid_interop call when time is involved
            %                 case 1
            %                     g = src.grid_interop(:);
            %                 case 2
            %                     g = src.grid_interop(:,:);
            %                 case 3
            %                     g = src.grid_interop(1,:,:);
            %                 case 4
            g = src.grid_interop(first, s, stride);
            %             end
            if g.z(1) <= g.z(2)
                e=[length(g.z) 1];
            else
                e=[1 length(g.z)];
            end
        end

        function e = extent(src)
            % NCGEOVARIABLE.extent - Function to find geographic bounding box coordinates.
            % Usage e = var.extent;
            s = src.size;
            lens = length(s);
            first = ones(1,lens);
            if lens > 1
                s(1) = 1;
                if lens > 3;
                    s(2) = 1;
                end
            end
            stride = first;
            %             switch length(s) % hopefully this speeds up the grid_interop call when time is involved
            %                 case 1
            %                     g = src.grid_interop(:);
            %                 case 2
            %                     g = src.grid_interop(:,:);
            %                 case 3
            %                     g = src.grid_interop(1,:,:);
            %                 case 4
            g = src.grid_interop(first, s, stride);
            %             end
            e.lon = [min(min(g.lon)) max(max(g.lon))];
            e.lat = [min(min(g.lat)) max(max(g.lat))];
        end % end extent

        function te = timeextent(src)
            % NCGEOVARIABLE.timeextent - Function to find the start and stop time of the variable.
            % Usage e = var.timeextent;
            s = src.size;
            lens = length(s);
            first = ones(1, lens);
            stride = first;
            last = first;
            last(1) = s(1);

            %                     g1 = src.grid_interop(first, first, stride);
            %                     g2 = src.grid_interop(s, s, stride);
            g = src.grid_interop(first, last, stride);

            %             te = [g1.time g2.time];
            if isfield(g, 'time')
                te = [min(g.time) max(g.time)];
            else
                error('NCGEOVARIABLE:TIMEEXTENT',...
                    'There appears to be no time axis associated with the variable.');
            end

        end % end timeextent

        function ig = grid_interop(src, first, last, stride, ignore)
            % NCGEOVARIABLE.GRID_INTEROP - Method to get the coordinate variables and their data as a
            % a structure with standardized field names for lat, lon, time, and z. Other coordinate variables
            % that are not recognized by netcdf-java as the previous four types have field names directly
            % taken from their variable names.
            % Usage: >> gridstruct = geovar.grid_interop(1,:,:,1:2:50);
            if nargin < 5
                ignore = {};
            end
            g = src.grid(first, last, stride);
            names = fieldnames(g);

            for i = 1:length(names); % loop through fields returned by grid
                tempname = names{i};
                javaaxisvar  =   src.dataset.netcdf.findVariable(tempname);
                try
                    % Expecting a uvar.nc2.dataset.CoordinateAxis here
                    type = char(javaaxisvar.getAxisType());
                catch me
                    % If it's not a CoordinateAxis set type to an empty
                    % string
                    type = '';
                end
                if isempty(type)
                    ig.(tempname) = g.(tempname);
                else
                    switch type
                        case 'Height'
                            if ~ismember('z', ignore)
                                pos_z = char(javaaxisvar.getPositive());
                                if strcmpi(pos_z, 'POSITIVE_DOWN')
                                    tmp = g.(tempname);
                                    ig.z = tmp.*-1; %adjust for positive direction
                                elseif strcmpi(pos_z, 'down')
                                    tmp = g.(tempname);
                                    ig.z = tmp.*-1; %adjust for positive direction
                                else
                                    ig.z = g.(tempname);
                                end
                            end
                        case 'GeoZ'
                            if ~ismember('z', ignore)
                                pos_z = char(javaaxisvar.getPositive());
                                z_sn = src.dataset.attribute(tempname, 'standard_name');
                                k = strfind(z_sn, 'ocean_s');
                                switch isempty(k)
                                    case 0
                                        %                       n = max(size(src.size));
                                        %                       trange = java.util.ArrayList(n);
                                        %                       zrange = trange;
                                        %                       xrange = trange;
                                        %                       yrange = trange;
                                        trange = ucar.ma2.Range(first(1) - 1, last(1) - 1, stride(1));
                                        zrange = ucar.ma2.Range(first(2) - 1, last(2) - 1, stride(2));
                                        xrange = ucar.ma2.Range(first(4) - 1, last(4) - 1, stride(4));
                                        yrange = ucar.ma2.Range(first(3) - 1, last(3) - 1, stride(3));
                                        %                       coordinates.GridCoordSys.getVerticalTransform.getCoordinateArray
                                        %                          temp = src.variable.getCoordinateSystems();
                                        griddataset = ucar.nc2.dt.grid.GridDataset.open(src.dataset.location);
                                        grid = griddataset.findGridByName(src.name);
                                            if ( length(grid) ~= 0 )
                                            grid = grid.getCoordinateSystem();
                                            subgrid = grid.getVerticalTransform();
                                        else
                                            if ~ismember(tempname, ignore)
                                                ig.(tempname) = g.(tempname);
                                            end
                                            continue % skip to next var
                                        end
                                        subgrid = subgrid.subset(trange, zrange, yrange, xrange); %tempfortesting arc 10/18
                                        %
                                        % It looks like this dataset (http://geoport.whoi.edu/thredds/dodsC/usgs/vault0/models/examples/bora_feb.nc)
                                        % works when the vertical transform isnt subset. NJTBX uses the same methodology but subsets the resulting
                                        % z coordinate field in matlab after calling the getCoordinateArray method, not before in the java.
                                        % Should I implement a try and then default to subsetting afterwards on catch? Or should I just always do it
                                        % after? I am choosing the former. Anyone have any thoughts on the matter? -acrosby
                                        try
                                            try
                                                try
                                                    for q = first(1):stride(1):last(1)
                                                        %                                                     grid = griddataset.findGridByName(src.name);
                                                        %                                                     subgrid = grid.getCoordinateSystem();
                                                        array = subgrid.getCoordinateArray(q-1);
                                                        ig.z(q, :, :, :) = array.copyToNDJavaArray();
                                                    end
                                                catch me
                                                    c = 1;
                                                    for q = first(1):stride(1):last(1)
                                                        %                                                     grid = griddataset.findGridByName(src.name);
                                                        %                                                     grid = grid.getCoordinateSystem();
                                                        subgrid = grid.getVerticalTransform();

                                                        array = subgrid.getCoordinateArray(q-1); % Issue 27 is failing here...
                                                        z(c, :, :, :) = array.copyToNDJavaArray();
                                                        c = c + 1;
                                                    end
                                                    ig.z = z(:, first(2):stride(2):last(2),  first(3):stride(3):last(3),  first(4):stride(4):last(4));
                                                end
                                            catch me
                                                array = subgrid.getCoordinateArray(0);
                                                ig.z = array.copyToNDJavaArray();
                                            end
                                        catch me
                                            disp('Could you please add the code you are trying to run to Issue 27 at the nctoolbox issue tracking site.');
                                            web http://code.google.com/p/nctoolbox/issues/detail?id=27
                                            me.throw()
                                            % me.error('There is a problem applying the vertical coordinate tranform and subsetting the resuting values.');
                                        end

                                    otherwise

                                        if strcmp(pos_z, 'POSITIVE_DOWN')
                                            tmp = g.(tempname);
                                            ig.z = tmp.*-1; %adjust for positive direction
                                        else
                                            ig.z = g.(tempname);
                                        end
                                end

                            end
                        case 'Time'
                            if ~ismember('time', ignore)
                                tmp = g.(tempname);
                                t_converted = src.dataset.time(tempname, tmp);
                                ig.time = t_converted;
                            end
                            %                     case 'RunTime'
                            %                       tmp = obj.dataset.data(name, vFirst, vLast, vStride);
                            %                       t_converted = obj.dataset.time(name, tmp);
                            %                       data.(type) = t_converted;

                        case 'Lon'
                            if ~ismember('lon', ignore)
                                tmp = g.(tempname);
                                if max(max(tmp)) > 360
                                    if min(min(tmp)) > 0
                                        tmp(tmp>360) = tmp(tmp>360)-360;
                                        tmp(tmp>360) = tmp(tmp>360)-360;
                                        tmp(tmp>360) = tmp(tmp>360)-360;
                                        tmp(tmp>180) = tmp(tmp>180)-360;
                                    else
                                        error('NCGEOVARIABLE:GEOIJ',...
                                            'Longitude contains values that follow both -180/180 and 0/360+ conventions; can not subset.');
                                    end
                                elseif min(tmp) <~ 0
                                    % Do nothing, we assume that input is in -180/180 conventions. And this means that the data is too.
                                elseif max(tmp) > 180
                                    tmp = tmp - 360;
                                end
                                ig.lon = double(tmp);
                            end

                        case 'Lat'
                            if ~ismember('lat', ignore)
                                ig.lat = double(g.(tempname));
                            end

                        case 'GeoY'
                            %ig.y = g.(tempname);
                            if exist('griddataset', 'var')
                                grid = griddataset.findGridByName(src.name);
                                grid = grid.getCoordinateSystem();
                            else
                                griddataset = ucar.nc2.dt.grid.GridDataset.open(src.dataset.location);
                                grid = griddataset.findGridByName(src.name);
                                grid = grid.getCoordinateSystem();
                            end
                            try
                                %ig.y = grid.getYHorizAxis;
                                %ig.x = grid.getXHorizAxis;
                                [x, y] = meshgrid(g.x,g.y);
                                s = size(x);
                                x = reshape(x, [1 numel(x)]);
                                y = reshape(y, [1 numel(y)]);
                                tempXY = [x; y];
                                projection = grid.getProjection();
                                tempLatLon = projection.projToLatLon(tempXY);
                                if ~isfield(ig, 'lat')
                                    if ~ismember('lat', ignore)
                                        ig.lat = reshape(tempLatLon(1,:), s);
                                    end
                                end
                                if ~isfield(ig, 'lon')
                                    if ~ismember('lon', ignore)
                                        ig.lon = reshape(tempLatLon(2,:), s);
                                    end
                                end
                            catch me
                            end

                        case 'GeoX'
                            %                             ig.x = g.(tempname);
                            %                             if exist('griddataset', 'var')
                            %                                 grid = griddataset.findGridByName(src.name);
                            %                                 grid = grid.getCoordinateSystem();
                            %                             else
                            %                                 griddataset = ucar.nc2.dt.grid.GridDataset.open(src.dataset.location);
                            %                                 grid = griddataset.findGridByName(src.name);
                            %                                 grid = grid.getCoordinateSystem();
                            %
                            %                             end
                            %                             try
                            %                                 ig.x = grid.getXHorizAxis;
                            %                                 projection = grid.getProjection();
                            %                                 ig.lon = projection.projToLatLon();
                            %                             catch me

                        otherwise
                            if ~ismember(tempname, ignore)
                                ig.(tempname) = g.(tempname);
                            end
                    end % end switch on type
                end % end is type empty or not if statement
            end % end loop through field names

        end % grid_interop end

        function tw = timewindow(src, varargin)
            % NCGEOVARIABLE.TIMEWINDOW - Function to pull the time coordinates within the specified
            % start and stop times from the variable object.
            % Usage: >> time = geovar.timewindow([2004 1 1 0 0 0], [2005 12 31 0 0 0]);
            %              >> time = geovar.timewindow(731947, 732677);
            if nargin < 3
                d = src.timewindowij(varargin{1});
            elseif nargin == 3
                d = src.timewindowij(varargin{1}, varargin{2});
            else
                error('NCGEOVARIABLE:TIMEWINDOW',...
                    'Too many input arugments.');
            end
            tw = d;
        end

        function tv = gettimevar(src)
            % NCGEOVARIABLE.gettimevar() gets the time variable associated with the ncgeovariable.
            tn = src.gettimename();
            tv = src.dataset.geovariable(tn);
        end

        function [lv, type] = getlonvar(src)
            % NCGEOVARIABLE.getlonvar() gets the longitude variable assiciated with the ncgeovariable.
            tn = src.getlonname();
            type = 'Lon';
            if isempty(tn)
                tn = src.getxname();
                type = 'GeoX';
            end
            lv = src.dataset.geovariable(tn);
        end

        function [lv, type] = getlatvar(src)
            % NCGEOVARIABLE.getlatvar() gets the latitude variable associated with the ncgeovariable.
            type = 'Lat';
            tn = src.getlatname();
            if isempty(tn)
                tn = src.getyname();
                type = 'GeoY';
            end
            lv = src.dataset.geovariable(tn);
        end

        function tn = gettimename(src)
            % NCGEOVARIABLE.gettimename() gets the name if the time variable associated with the ncgeovariable
            for i = 1:length(src.axes)
                tempname = src.axes{i};
                javaaxisvar  =   src.dataset.netcdf.findVariable(tempname);
                type{i} = char(javaaxisvar.getAxisType());
            end
            match = strcmp('Time', type);
            tn = src.axes(match);
        end

        function ln = getlonname(src)
            % NCGEOVARIABLE.getlonname() gets the name of the longitude variable associated with the ncgeovariable.
            for i = 1:length(src.axes)
                tempname = src.axes{i};
                javaaxisvar  =   src.dataset.netcdf.findVariable(tempname);
                type{i} = char(javaaxisvar.getAxisType());
            end
            match = strcmp('Lon', type);
            ln = src.axes(match);
        end

        function ln = getlatname(src)
            % NCGEOVARIABLE.gelatname() gets the naem of the latitude variable associated with the ncgeovariable
            for i = 1:length(src.axes)
                tempname = src.axes{i};
                javaaxisvar  =   src.dataset.netcdf.findVariable(tempname);
                type{i} = char(javaaxisvar.getAxisType());
            end
            match = strcmp('Lat', type);
            ln = src.axes(match);
        end

        function ln = getxname(src)
            % NCGEOVARIABLE.getxname() gets the name of the x variable associated with the ncgeovariable.
            for i = 1:length(src.axes)
                tempname = src.axes{i};
                javaaxisvar  =   src.dataset.netcdf.findVariable(tempname);
                type{i} = char(javaaxisvar.getAxisType());
            end
            match = strcmp('GeoX', type);
            ln = src.axes(match);
        end

        function ln = getyname(src)
            % NCGEOVARIABLE.getyname() gets the name of the y variable assoxiated with the ncgeovariable
            for i = 1:length(src.axes)
                tempname = src.axes{i};
                javaaxisvar  =   src.dataset.netcdf.findVariable(tempname);
                type{i} = char(javaaxisvar.getAxisType());
            end
            match = strcmp('GeoY', type);
            ln = src.axes(match);
        end

        function s = gettimedata(src, start, last, stride)
            % NCGEOVARIABLE.gettimedata()
            %             var = src.gettimevar;
            %             tn = var.data(start, last, stride);
            %             tn = var.dataset.time(src.gettimename, tn);

            v = src.gettimevar;
            sz = src.size();
            timesize = v.size();
            timelocation = find(sz==timesize(1));

            timestart = start(timelocation);
            timelast = last(timelocation);
            timestride = stride(timelocation);

            s = v.data(timestart:timestride:timelast);

            s = src.dataset.time(v.name, s);
        end

        function [s, type] = getlondata(src, start, last, stride)
            % NCGEOVARIABLE.getlondata() gets the longitude data associated with the ncgeovariable.
            [v, type] = src.getlonvar;
            sz = src.size();
            lonsize = v.size();
            lonlocation = find(sz==lonsize(1));
            if length(lonsize) == 1
                lonstart = start(lonlocation);
                lonlast = last(lonlocation);
                lonstride = stride(lonlocation);
            else
                lonstart = start(lonlocation:lonlocation+1);
                lonlast = last(lonlocation:lonlocation+1);
                lonstride = stride(lonlocation:lonlocation+1);
            end
            switch length(lonsize)
                case 1
                    s = v.data(lonstart:lonstride:lonlast);
                case 2
                    s = v.data(lonstart(1):lonstride(1):lonlast(1),lonstart(2):lonstride(2):lonlast(2));
                case 3
                    s = v.data(lonstart(1):lonstride(1):lonlast(1), lonstart(2):lonstride(2):lonlast(2), lonstart(3):lonstride(3):lonlast(3));
                case 4
                    s = v.data(lonstart(1):lonstride(1):lonlast(1), lonstart(2):lonstride(2):lonlast(2), lonstart(3):lonstride(3):lonlast(3),...
                        lonstart(4):lonstride(4):lonlast(4));
            end
            %               s = v.data(lonstart, lonlast, lonstride);
        end

        function [s, type] = getlatdata(src, start, last, stride)
            % NCGEOVARIABLE.gelatdata() gets the latitude data associated with the ncgeovariable.
            [v, type] = src.getlatvar;
            sz = src.size();
            latsize = v.size();
            latlocation = find(sz==latsize(1));
            if length(latsize) == 1
                latstart = start(latlocation);
                latlast = last(latlocation);
                latstride = stride(latlocation);
            else
                latstart = start(latlocation:latlocation+1);
                latlast = last(latlocation:latlocation+1);
                latstride = stride(latlocation:latlocation+1);
            end
            switch length(latsize)
                case 1
                    s = v.data(latstart:latstride:latlast);
                case 2
                    s = v.data(latstart(1):latstride(1):latlast(1),latstart(2):latstride(2):latlast(2));
                case 3
                    s = v.data(latstart(1):latstride(1):latlast(1), latstart(2):latstride(2):latlast(2), latstart(3):latstride(3):latlast(3));
                case 4
                    s = v.data(latstart(1):latstride(1):latlast(1), latstart(2):latstride(2):latlast(2), latstart(3):latstride(3):latlast(3),...
                        latstart(4):latstride(4):latlast(4));
            end
            %                s = v.data(latstart, latlast, latstride);
        end

        function vec = getvectors(src, u, v, alpha)
            % NCGEOVARIABLE.getvectors - Function to average and rotate
            % u and v ROMS vectors on to a common grid (grid is based on
            % the coordinates of the geovariable that "getvectors" is called
            % from.)
            % Use:
            %     >> uvar = nc.geovariable('u');
            %     >> vvar = nc.geovariable('v');
            %     >> hvar = nc.geovariable('depth');
            %     >> avar = nc.geovariable('angle');
            %     >> U = hvar.getvectors(uvar, vvar, avar); % complex matrix is returned
            if nargin < 4
                vec = complex_ongrid(src, u, v);
            elseif nargin == 4
                vec = complex_ongrid(src, u, v, alpha);
            else
                error(['NCTOOLBOX:ncgeovariable:getvectors'], ...
                    'Incorrect number of arguments for getvectrs()');
            end
        end

        %% These functions would rather output multiple outputs instead of struct, must reconcile
        %     with the subsref in either ncgeovariable or ncvariable. Wait, why, then, does geoij work???
        function d = timewindowij(src, varargin)
            % NCGEOVARIABLE.TIMEWINDOWIJ - Function to get indices from start and stop times for sub-
            % setting. TODO: There must be a better/fast way to do this using the java library.
            % Useage: >> timestruct = geovar.timewindowij([2004 1 1 0 0 0], [2005 12 31 0 0 0]);
            %              >> timestruct = geovar.timewindowij(731947, 732677);
            % Result: time.time = time data
            %            time.index = time indices
            s = src.size;
            first = ones(1, length(s));
            last = s;
            stride = first;
            g.time = src.gettimedata(first, last, stride);

            if isfield(g, 'time') % are any of the fields recognized as time explictly
                if nargin > 2 % If two times are input, do window
                    starttime = datenum(varargin{1});
                    stoptime = datenum(varargin{2});
                    if isempty(starttime)
                        starttime = g.time(1);
                    end
                    if isempty(stoptime)
                        stoptime = g.time(end);
                    end

                    t_index1 = g.time >= starttime;
                    t_index2 = g.time <= stoptime;
                    d.index = find(t_index1==t_index2);
                    d.time = g.time(d.index);
                elseif nargin < 3 % If theres only one time, do nearest
                    neartime = datenum(varargin{1});
                    diff = abs(g.time-neartime);
                    ind = find(diff==min(diff));
                    d.index = ind(1);
                    d.time = g.time(d.index);
                    if length(ind) > 1;
                        warning('NCGEOVARIABLE:TIMEWINDOWIJ',...
                            ['Multiple time indices determined to be nearest to the supplied time,'...
                            'only the first index was output.']);
                    end
                end
            else
                me = MException(['NCTOOLBOX:ncgeovariable:timewindowij'], ...
                    'No grid variable returned as time.');
                me.throw;
            end
        end % end timewindowij

        function d = geosubset(obj, struct, varargin)
            % NCGEOVARIABLE.GEOSUBSET -
            % Create subset structure:
            % s.time=[now-3 now]; % Can be omitted or s.t_index=[i1 i2]; can be used for subsetting time using indices
            % s.t_stride=2; % Can be omitted
            % s.lat=[22 27];
            % s.lon=[117 123];
            % s.h_stride=[2 2]; % Can be omitted
            % s.z_index=[1 30]; % Can be omitted
            % or
            % s.z_index='top'
            % s.z_index='bottom'
            % s.v_stride=2; % Can be omitted
            %
            % Usage:
            % sub = gvar.geosubset(s); % Subset method
            %
            % Returns:
            % Structure with fields 'data' for data values, and 'grid' for structure of coordinate variables
            % (that come from grid_interop).

            %           if ~regexp(obj.dataset.attribute('Conventions'), 'UGRID')
            nums = obj.size;
            if isfield(struct, 'h_stride');
                if length(struct.h_stride) < 2
                    struct.h_stride(1, 2) = struct.h_stride(1,1); % Resoltution to Issue 24
                end
            else
                struct.h_stride = [1 1];
            end

            if isfield(struct, 'v_stride');
            else
                struct.v_stride = 1;
            end

            if isfield(struct, 't_stride');
            else
                struct.t_stride = 1;
            end

            [indstart_r indend_r indstart_c indend_c] = obj.geoij(struct);

            if ~isempty(indstart_r)
                if isfield(struct, 'time') % Deal with time (values) or t_index (indices) bounds
                    if iscell(struct.time)
                        switch length(struct.time)
                            case 1
                                t = obj.timewindowij(struct.time{1});
                                tmin_i = t.index;
                                tmax_i = t.index;
                            case 2
                                t = obj.timewindowij(struct.time{1}, struct.time{2});
                                tmin_i = min(t.index);
                                tmax_i = max(t.index);
                        end
                    else
                        if ischar(struct.time)
                            t = obj.timewindowij(struct.time);
                            tmin_i = t.index;
                            tmax_i = t.index;
                        elseif length(struct.time) == 6
                            t = obj.timewindowij(struct.time);
                            tmin_i = t.index;
                            tmax_i = t.index;
                        else
                            switch length(struct.time)
                                case 1
                                    t = obj.timewindowij(struct.time);
                                    tmin_i = t.index;
                                    tmax_i = t.index;
                                case 2
                                    t = obj.timewindowij(struct.time(1), struct.time(2));
                                    tmin_i = min(t.index);
                                    tmax_i = max(t.index);
                                otherwise % for anything else assume that it is a single datevec
                                    t = obj.timewindowij(struct.time);
                                    tmin_i = t.index;
                                    tmax_i = t.index;
                            end
                        end
                    end
                elseif isfield(struct, 't_index') % check for 1 time index not the same time index twice $$$$$$$$$$$$
                    if iscell(struct.t_index)
                        switch length(t_index)
                            case 1
                                if numel(struct.t_index{1}) > 1 % check to see if someone used str or datevec by accident
                                    me = MException(['NCTOOLBOX:ncgeovariable:geosubset'], ...
                                        'Expected min time to be an index/integer.');
                                    me.throw;
                                else
                                    tmin_i = struct.t_index{1};
                                end
                            case 2
                                if numel(struct.t_index{1}) > 1 % check to see if someone used str or datevec by accident
                                    me = MException(['NCTOOLBOX:ncgeovariable:geosubset'], ...
                                        'Expected min time to be an index/integer.');
                                    me.throw;
                                else
                                    tmin_i = struct.t_index{1};
                                end
                                if numel(struct.t_index{2}) > 1 % check to see if someone used str or datevec by accident
                                    me = MException(['NCTOOLBOX:' mfilename ':geosubset'], ...
                                        'Expected max time to be an index/integer.');
                                    me.throw;
                                else
                                    tmax_i = struct.t_index{2};
                                end
                        end
                    else
                        switch length(struct.t_index)
                            case 1
                                tmin_i = struct.t_index(1);
                                tmax_i = struct.t_index(1);
                            case 2
                                tmin_i = struct.t_index(1);
                                tmax_i = struct.t_index(2);
                        end
                    end
                else
                    tmin_i = 1;
                    tmax_i = nums(1);
                end

                order = obj.getaxesorder;

                if isfield(struct, 'z_index')
                    if length(struct.z_index)==1 || ischar(struct.z_index)
                        if ischar(struct.z_index)
                           e=obj.top_bot;
                           switch struct.z_index
                                case 'top'
                                    struct.z_index=e(1);
                                    struct.z_index=e(1);

                                case 'bottom'
                                    struct.z_index=e(2);
                                    struct.z_index=e(2);                         end
                        end
                        zmin = struct.z_index;
                        zmax = struct.z_index;

                    elseif length(struct.z_index)==2
                        zmin = struct.z_index(1);
                        zmax = struct.z_index(2);
                    end
                else
                    if isfield(order, 'z')
                        zmin = 1;
                        zmax = nums(order.z);
                    else
                        order.z = [];
                        zmin = [];
                        zmax = [];
                    end
                end

                if length(nums) < 2
                    me = MException(['NCTOOLBOX:ncgeovariable:geosubset'], ...
                        ['Expected data of ', obj.name, ' to be at least rank 2.']);
                    me.throw;
                else
                    first = ones([1, length(nums)]);
                    stride = ones([1, length(nums)]);
                    last = nums;

                    if order.lon ~= order.lat
                        % pass
                    else
                        order.lat = order.lon;
                        order.lon = order.lat+1;
                    end
                    try
                        stride(order.time) = struct.t_stride;
                        first(order.time)   = tmin_i;
                        last(order.time)   = tmax_i;
                    end
                    try
                        stride(order.z) = struct.v_stride;
                        first(order.z) = zmin;
                        last(order.z)         = zmax;
                    end
                    stride(order.lon)     = struct.h_stride(2);
                    stride(order.lat)      = struct.h_stride(1);

                    first(order.lon)     = indstart_c;
                    first(order.lat)      = indstart_r;

                    last(order.lon)     = indend_c;
                    last(order.lat)      = indend_r;

                    %                 else
                    %                     me = MException(['NCTOOLBOX:ncgeovariable:geosubset'], ...
                    %                         ['Expected shape of data of ', obj.name, ' to be less than rank 5.']);
                    %                     me.throw;
                end

                % Get the corresponding data and interop grid...
                d.data = obj.data(first, last, stride);
                d.grid = obj.grid_interop(first, last, stride, varargin);
                d.start = first;
                d.stop = last;
                d.stride = stride;
                for i = 1:length(first)
                    d.indices{i} = first(i):stride(i):last(i);
                end
                %           else
                %             ugrid = ncugrid(obj); % Starting to add place holders for ugrid subsetting functionality
                %             d = ugrid.unstructuredLatLonSubset(struct);
                %           end % end of ugrid if
            else
                me = MException(['NCTOOLBOX:ncgeovariable:geosubset'], ...
                    ['No indices returned cooresponding to geographic subset.']);
                me.throw;
            end
        end % end of geosubset
        %%
        function [indstart_r indend_r indstart_c indend_c] =...
                geoij(obj, struct)
            % GEOVARIABLE.GEOIJ - Function to return start and stop indices for rows and columns if the
            % the lat/lon grids are rank 2, but if the grids are vectors the function returns start and stop
            % indices of lon and then lat (in that order).
            %
            % Usage: >> [firstrow, lastrow, firstcol, lastcol] = geoij(geovar, subsetstruct)
            %            >> [firstlon, lastlon, firstlat, lastlat] = geoij(geovar, subsetstruct) % if lat/lon are vector
            %
            %
            %             s = obj.size;
            %             first = ones(1, length(s));
            %             last = s;
            %             stride = first;
            %             g = obj.grid_interop(first, last, stride); % this is doing the whole thing
            %           h = 0;
            flag = 0;

            warning off
            switch  length(obj.size)
                case 1
                    [g.lon, typex] = obj.getlondata(1, obj.size, 1);
                    [g.lat, typey] = obj.getlatdata(1, obj.size, 1);
                case 2
                    [g.lon, typex] = obj.getlondata([1, 1], obj.size, [1, 1]);
                    [g.lat, typey] = obj.getlatdata([1, 1], obj.size, [1, 1]);
                case 3
                    [g.lon, typex] = obj.getlondata([1, 1, 1], obj.size, [1, 1, 1]);
                    [g.lat, typey] = obj.getlatdata([1, 1, 1], obj.size, [1, 1, 1]);
                case 4
                    [g.lon, typex] = obj.getlondata([1, 1, 1, 1], obj.size, [1, 1, 1, 1]);
                    [g.lat, typey] = obj.getlatdata([1, 1, 1, 1], obj.size, [1, 1, 1, 1]);
            end
            warning on
            if strcmp(typex, 'GeoX')
                griddataset = ucar.nc2.dt.grid.GridDataset.open(obj.dataset.location);
                grid = griddataset.findGridByName(obj.name);
                grid = grid.getCoordinateSystem();

                %ig.y = grid.getYHorizAxis;
                %ig.x = grid.getXHorizAxis;
                [x, y] = meshgrid(g.lon,g.lat);
                s = size(x);
                x = reshape(x, [1 numel(x)]);
                y = reshape(y, [1 numel(y)]);
                tempXY = [x; y];
                projection = grid.getProjection();
                tempLatLon = projection.projToLatLon(tempXY);

                g.lat = reshape(tempLatLon(1,:), s);
                g.lon = reshape(tempLatLon(2,:), s);
            end

            g.lon = double(g.lon);
            g.lat = double(g.lat);

            if max(g.lon) > 360
                if min(g.lon) > 0
                    g.lon(g.lon>360) = g.lon(g.lon>360)-360;
                    g.lon(g.lon>360) = g.lon(g.lon>360)-360;
                    g.lon(g.lon>360) = g.lon(g.lon>360)-360;
                    g.lon(g.lon>180) = g.lon(g.lon>180)-360;
                else
                    error('NCGEOVARIABLE:GEOIJ',...
                        'Longitude contains values that follow both -180/180 and 0/360+ conventions; can not subset.');
                end
            elseif min(g.lon) < 0
                % Do nothing, we assume that input is in -180/180 conventions. And this means that the data is too.
            elseif max(g.lon) > 180
                g.lon = g.lon - 360;
            end

            %Unpack geosubset_structure
            if isfield(struct, 'lat');
                switch length(struct.lat)
                    case 1
                        flag = 1;
                    case 2
                        north_max = struct.lat(2);
                        north_min = struct.lat(1);
                end

            else
                north_max = max(max(g.lat));
                north_min = min(min(g.lat));
            end

            if isfield(struct,'lon');
                switch length(struct.lon)
                    case 1
                        flag = 1;
                    case 2
                        east_max = struct.lon(2);
                        east_min = struct.lon(1);
                end

            else
                east_max = max(max(g.lon));
                east_min = max(min(g.lon));
            end

            switch flag
                case 0
                    if ~isvector(g.lat)
                        [indlat_l1] = ((g.lat <= north_max)); %2d
                        [indlat_l2] = ((g.lat >= north_min)); %2d
                        [indlat_r indlat_c] = find((indlat_l1&indlat_l2)); % 1d each
                        indlon_l1 = zeros(size(indlat_l1));
                        indlon_l2 = zeros(size(indlat_l1));
                        for i = 1:length(indlat_r)
                            if g.lon(indlat_r(i), indlat_c(i)) <= east_max;
                                indlon_l1(indlat_r(i), indlat_c(i)) = true;
                            end
                            if g.lon(indlat_r(i), indlat_c(i)) >= east_min;
                                indlon_l2(indlat_r(i), indlat_c(i)) = true;
                            end
                        end
                        [ind_r, ind_c] = find((indlon_l1&indlon_l2));
                        indstart_c = min(ind_c); % Out
                        indend_c = max(ind_c);
                        indstart_r = min(ind_r);
                        indend_r = max(ind_r);
                    else
                        indlat1 = (g.lat <= north_max);
                        indlat2 = (g.lat >= north_min);
                        indlat = find(indlat1&indlat2);
                        indlon1 = (g.lon <= east_max);
                        indlon2 = (g.lon >= east_min);
                        indlon = find(indlon1&indlon2);
                        % Out
                        attrs = obj.dataset.attributes;
                        key = value4key(attrs, 'CF:featureType');

                        if strcmp(key, 'timeSeries')
                            a = [];
                            for i = 1:length(indlon)
                                if ~isempty(find(indlon(i)==indlat, 1))
                                    a(length(a)+1) = indlon(i);
                                end
                            end
                            indstart_c = min(a);
                            indend_c = max(a);
                            indstart_r = min(a);
                            indend_r = max(a);
                        else
                            indstart_c = min(indlon); % testing temp, i switched lon and lat here
                            indend_c = max(indlon);
                            indstart_r = min(indlat);
                            indend_r = max(indlat);
                            %
                        end
                    end
                case 1
                    if isvector(g.lat)
                        indstart_c = near(g.lon,struct.lon);
                        indstart_r = near(g.lat,struct.lat);
                        indend_c = indstart_c;
                        indend_r = indstart_r;
                    else
                        % This way does't require the addtional step, or the additional function and is verified
                        % to return the same results as using nearxy with first output and ind2ij.
                        % Ind2ij doesn't seem to add any value to the toolbox, everything is already here to
                        % get rows and columns. The third nearxy output can remain private if it confusing to
                        % users (no mention of it in help docs).
                        [~, ~, indexes] = near_lonlat(g.lon, g.lat, struct.lon, struct.lat);
                        indstart_c = indexes(2);
                        indend_c = indexes(2);
                        indstart_r = indexes(1);
                        indend_r = indexes(1);
                    end
            end
        end % end geoij

        function order = getaxesorder(obj)
            %  NCGEOVARIABLE.getaxesorder  gets the order of each axis of the ncgeovariable

            %             permute_nums = fliplr(obj.axes_info);
            ainfo = obj.axes_info;
            siz = obj.size;
            for i = 1:length(ainfo(:, 1))
                order.(ainfo{i,1}) = find(siz==ainfo{i, 2}(1));
            end

        end

        function sref = subsref(obj,s)
	    % NCGEOVARIABLE.subsref applies various .name and () methods to the ncgeovariable
            switch s(1).type
                % Use the built-in subsref for dot notation
                case '.'
                    switch s(1).subs
                        case 'gettimedata'
                            switch length(s)
                                case 1
                                    sref = obj;
                                case 2
                                    nums = obj.size;
                                    [first last stride] = indexing(s(2).subs, double(nums));
                                    sref = obj.gettimedata(first, last, stride);
                            end
                        case 'getlondata'
                            switch length(s)
                                case 1
                                    sref = obj;
                                case 2
                                    nums = obj.size;
                                    [first last stride] = indexing(s(2).subs, double(nums));
                                    sref = obj.getlondata(first, last, stride);
                            end
                        case 'getlatdata'
                            switch length(s)
                                case 1
                                    sref = obj;
                                case 2
                                    nums = obj.size;
                                    [first last stride] = indexing(s(2).subs, double(nums));
                                    sref = obj.getlatdata(first, last, stride);
                            end
                        case 'grid_interop'
                            switch length(s)
                                case 1
                                    sref = obj;
                                case 2
                                    nums = obj.size;
                                    [first last stride] = indexing(s(2).subs, double(nums));
                                    sref = obj.grid_interop(first, last, stride);
                            end
                        case 'data'
                            nums = size(obj);
                            if ~isempty(nums)
                                switch length(s)
                                    case 1
                                        sref = obj;
                                    case 2
                                        [first last stride] = indexing(s(2).subs, double(nums));
                                        sref = obj.data(first, last, stride);
                                end

                            else
                                sref = obj.data;
                                warning(['NCTOOLBOX:ncgeovariable:subsref'], ...
                                    ['Variable "' name '" has no netcdf dimension associated with it. Errors may result from non CF compliant files.'])
                            end
                        case 'grid'
                            nums = size(obj);
                            if ~isempty(nums)
                                switch length(s)
                                    case 1
                                        sref = obj;
                                    case 2
                                        [first last stride] = indexing(s(2).subs, double(nums));
                                        sref = obj.grid(first, last, stride);
                                end

                            else
                                warning(['NCTOOLBOX:ncgeovariable:subsref'], ...
                                    ['Variable "' name '" has no netcdf dimension associated with it. Errors may result from non CF compliant files.'])
                            end
                        otherwise
                            sref = builtin('subsref',obj,s);
                    end
                case '()'
                    [first last stride] = indexing(s(1).subs, double(size(obj)));
                    sref = obj.data(first, last, stride);
                case '{}'
                    warning(['NCTOOLBOX:ncgeovariable:subsref'], ...
                        'Not a supported subscripted reference, "{}" are not permitted to call variable object methods');
            end
        end
    end % methods end

    %     methods (Access = protected)
    %
    %     end % protected methods end
end % class end


function varargout = lz4decode(varargin)
%
% output = lz4decode(input)
%    or
% output = lz4decode(input,info)
%
% Decompressing an LZ4-compressed byte-stream to recover the original data
% This function depends on the ZMat toolbox (http://github.com/fangq/zmat)
%
% authors:Qianqian Fang (q.fang <at> neu.edu)
%
% input:
%      input: a string, int8/uint8 vector or numerical array to store LZ4-compressed data
%      info (optional): a struct produced by the zmat/lz4encode function during
%            compression; if not given, the inputs/outputs will be treated as a
%            1-D vector
%
% output:
%      output: the decompressed byte stream stored in a uint8 vector; if info is
%            given, output will restore the original data's type and dimensions
%
% examples:
%      [bytes, info]=lz4encode(eye(10));
%      orig=lz4decode(bytes,info);
%
% license:
%     BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details
%
% -- this function is part of JSONLab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab)
%

if(nargin==0)
    error('you must provide at least 1 input');
end
if(exist('zmat','file')==2 || exist('zmat','file')==3)
    if(nargin>1)
        [varargout{1:nargout}]=zmat(varargin{1},varargin{2:end});
    else
        [varargout{1:nargout}]=zmat(varargin{1},0,'lz4',varargin{2:end});
    end
else
    error('you must install ZMat toolbox to use this feature: http://github.com/fangq/zmat')
end


function opt= defopt_scalp_power(varargin)
%DEFOPT_SCALP_POWER - Properties for plot_scalp* functions, optimized for
%visualizing spectral power

props= {'Shading'                 'flat'
        'Extrapolation'           1
        'ExtrapolateToMean'       0
        'Resolution'              61
        'CLim'                    'range'
        'IvalColor'               [0.8 0.8 0.8; 0.6 0.6 0.6]
        'LineWidth'               2
        'ChannelLineStyleOrder'   {'thick' 'thin'}
        'Colormap'                cmap_hsvFade(51, [4/6 0], 1, 1)
        'Contour'                 5
        'ContourPolicy'           'strict'
        'ContourLineprop'         {'LineWidth' 0.3}
        'MarkMarkerProperties'    {'MarkerSize' 6, 'LineWidth' 1}
        'GlobalCLim'              1
        'LegendPos'               'NorthEast'};

opt= opt_proplistToStruct(varargin{:});
opt= opt_setDefaults(opt, props);


% MatrixUser, a multi-dimensional matrix analysis software package
% https://sourceforge.net/projects/matrixuser/
%
% The MatrixUser is a matrix analysis software package developed under Matlab
% Graphical User Interface Developing Environment (GUIDE). It features
% functions that are designed and optimized for working with multi-dimensional
% matrix under Matlab. These functions typically includes functions for
% multi-dimensional matrix display, matrix (image stack) analysis and matrix
% processing.
%
% Author:
%   Fang Liu <leoliuf@gmail.com>
%   University of Wisconsin-Madison
%   Aug-30-2014

function varargout = About_MU(varargin)
% ABOUT_MU M-file for About_MU.fig
%      ABOUT_MU, by itself, creates a new ABOUT_MU or raises the existing
%      singleton*.
%
%      H = ABOUT_MU returns the handle to a new ABOUT_MU or the handle to
%      the existing singleton*.
%
%      ABOUT_MU('CALLBACK',hObject,eventData,handles,...) calls the local
%      function named CALLBACK in ABOUT_MU.M with the given input arguments.
%
%      ABOUT_MU('Property','Value',...) creates a new ABOUT_MU or raises the
%      existing singleton*.  Starting from the left, property value pairs are
%      applied to the GUI before About_MU_OpeningFcn gets called.  An
%      unrecognized property name or invalid value makes property application
%      stop.  All inputs are passed to About_MU_OpeningFcn via varargin.
%
%      *See GUI Options on GUIDE's Tools menu.  Choose "GUI allows only one
%      instance to run (singleton)".
%
% See also: GUIDE, GUIDATA, GUIHANDLES

% Edit the above text to modify the response to help About_MU

% Last Modified by GUIDE v2.5 07-Nov-2011 13:21:51

% Begin initialization code - DO NOT EDIT
gui_Singleton = 0;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @About_MU_OpeningFcn, ...
                   'gui_OutputFcn',  @About_MU_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT

% --- Executes just before About_MU is made visible.
function About_MU_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% varargin   command line arguments to About_MU (see VARARGIN)

% Choose default command line output for About_MU
handles.output = hObject;

% Update handles structure
guidata(hObject, handles);

% UIWAIT makes About_MU wait for user response (see UIRESUME)
% uiwait(handles.figure1);

% --- Outputs from this function are returned to the command line.
function varargout = About_MU_OutputFcn(hObject, eventdata, handles)
% varargout  cell array for returning output args (see VARARGOUT);
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Get default command line output from handles structure
varargout{1} = handles.output;


function model = addCOBRAVariables(model, idList, varargin)
% Add a Variable to the COBRA model provided. These variables can be
% referred to by additional constraints added via addCOBRAConstraint. Their
% IDs have to be mutually exclusive with the IDs in model.rxns
% USAGE:
%    model = addCOBRAVariables(model, idList, varargin)
%
% INPUTS:
%    model:         model structure
%    idList:        cell array of ids
%
%    varargin:
%                   * lb:               The lower bounds of the variables
%                                       Has to be of the same size as
%                                       idlist
%                                       (default: -1000)
%                   * ub:               The upper bounds of the variables.
%                                       Has to be of the same size as
%                                       idlist
%                                       (default: 1000)
%                   * c:                The objective coefficient of the variables
%                                       Has to be of the same size as idlist
%                                       (default: 0)
%                   * Names:            Descriptive names of the variables.
%                                       (default: idList)
% OUTPUT:
%    model:         constrained model containing the new variables in the
%                   respective fields (`E`,`evars`,`evarub`,`evarlb`,`evarc`)
%                    * `.E` - The coefficient matrix for coefficients of metabolites for the variables
%                    * `.evars` - The variable IDs
%                    * `.evarlb` - The variable lower bounds
%                    * `.evarub` - The variable upper bounds
%                    * `.evarc` - The variable objective coefficient
%                    *  Optional: `D`, the matrix conatining coefficients for additional variables.
% NOTE:
%    This function will, if not present create the `E`, `evars`, `evarlb`, `evarub`, `evarc` and,
%    if additional Constraints (i.e. the `C` matrix) is present, the `D`
%    field as defined in the model field definitions. The `E` matrix containts
%    the Variable coefficients referring to metabolites, while the `D`
%    field contains the variable coefficients referring to additional
%    Constraints from `C`. `evars` contains the IDs of the additional
%    variables, with one entrey per column of `E`. `evarlb` and `evarub`
%    represent the lower and upper bounds of the variables respectively.
%    `evarc` are objective coefficients of the variables.
%
% Author: Thomas Pfau, Oct 2018

if ischar(idList)
    %This is an individual element.
    idList = {idList};
end

p = inputParser();
p.CaseSensitive = false;
p.addRequired('model',@isstruct);
p.addRequired('idList',@iscell);
p.addParameter('lb',columnVector(-1000*ones(size(idList))),@(x) isnumeric(x) && (numel(x) == numel(idList) || numel(x) == 1));
p.addParameter('ub',columnVector(1000*ones(size(idList))),@(x) isnumeric(x) && (numel(x) == numel(idList) || numel(x) == 1));
p.addParameter('c',columnVector(zeros(size(idList))),@(x) isnumeric(x) &&( numel(x) == numel(idList) || numel(x) == 1));
p.addParameter('Names',idList, @(x) (iscell(x) && numel(x) == numel(idList)) || ischar(x) && numel(idList == 1));
p.parse(model,idList,varargin{:});

lb = p.Results.lb;
if numel(lb) == 1
    lb = ones(numel(idList),1)*lb;
end

ub = p.Results.ub;
if numel(ub) == 1
    ub = ones(numel(idList),1)*ub;
end

c = p.Results.c;
if numel(c) == 1
    c = ones(numel(idList),1)*c;
end

if any(ismember(model.rxns,idList))
    duplicateIDs = unique(model.rxns(ismember(model.rxns,idList)));
    error('The following IDs are already IDs of reactions:\n%s\n',strjoin(duplicateIDs,'\n'));
end

% no variables exist yet, so we need to build the fields.
if ~isfield(model,'evars')
    % we need to create new fields
    if ~isfield(model,'ctrs')
        fieldsToCreate = {'E','evars','evarlb','evarub','evarc'};
    else
        fieldsToCreate = {'E','evars','evarlb','evarub','evarc','D'};
    end
    model = createEmptyFields(model,fieldsToCreate);
else
    % check, that the IDs are valid.
    if any(ismember(model.evars,idList))
        duplicateIDs = unique(model.evars(ismember(model.evars,idList)));
        error('The following IDs are already IDs of variables:\n%s\n',strjoin(duplicateIDs,'\n'));
    end
end

% this is all checks done, now lets get to work.
[~,nVars] = size(model.E);

if ~isfield(model,'evarNames') && ~any(strcmp(p.UsingDefaults,'Names'))
    % if the field does not yet exist, and we got actual names, we need to
    % create the field.
    model = createEmptyFields(model,{'evarNames'});
end

% extend the IDs.
model.evars = [model.evars; columnVector(idList)];
model.evarlb = [model.evarlb; columnVector(lb)];
model.evarub = [model.evarub; columnVector(ub)];
model.evarc = [model.evarc; columnVector(c)];

if isfield(model,'evarNames')
    model.evarNames = [model.evarNames; columnVector(p.Results.Names)];
end

% extend other fields as necessary.
model = extendModelFieldsForType(model,'evars','originalSize',nVars);


function NoiseCovFiles = bst_noisecov(iTargetStudies, iDataStudies, iDatas, Options, isDataCov)
% BST_NOISECOV: Compute noise covariance matrix for a set of studies.
%
% USAGE:  NoiseCovFiles = bst_noisecov(iTargetStudies, iDataStudies, iDatas, Options=[ask], isDataCov=0)
%               Options = bst_noisecov()
%
% INPUT:
%     - iTargetStudies : List of studies indices for which the noise covariance matrix is produced
%     - iDataStudies   : [1,nData] int, List of data files to use for computation (studies indices)
%                        If not defined or [], uses all the recordings from all the studies (iTargetStudies)
%     - iDatas         : [1,nData] int, List of data files to use for computation (data indices)
%     - isDataCov      : If 1, saves the result as the data covariance, if 0 saves it as the noise covariance
%     - Options        : Structure with the following fields (if not defined: asked to the user)
%           |- Baseline        : [tStart, tStop]; range of time values considered as baseline
%           |- RemoveDcOffset  : {'file', 'all'}; 'all' removes the baseline avg file by file; 'all' computes the baseline avg from all the files
%           |- ReplaceFile     : If 1 replaces automatically the previous noisecov file without asking the user for a confirmation
%           |- ChannelTypes    : Cell array with the list of selected channel types

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, 2009-2022

%% ===== RETURN DEFAULT OPTIONS =====
% Options structure
if (nargin == 0)
    NoiseCovFiles = struct(...
        'Baseline',        [-.1, 0], ...
        'DataTimeWindow',  [0, 1], ...
        'RemoveDcOffset',  'file', ...
        'ReplaceFile',     [], ...
        'ChannelTypes',    []);
    return;
end

%% ===== PARSE INPUTS =====
if (nargin < 5) || isempty(isDataCov)
    isDataCov = [];
end
if (nargin < 4) || isempty(Options)
    Options = [];
end
NoiseCovFiles = {};
isRaw = 0;
% Get source files
if (nargin < 3) || isempty(iDataStudies) || (length(iDataStudies) ~= length(iDatas))
    % Get all the datafiles depending on the target studies
    sStudies = bst_get('Study', iTargetStudies);
    sDatas = [sStudies.Data];
    DataFiles = {sDatas.FileName};
else
    % Unique studies
    uniqueStudies = unique(iDataStudies);
    DataFiles = {};
    % For each study
    for i = 1:length(uniqueStudies)
        % Get study
        iStudy = uniqueStudies(i);
        sStudy = bst_get('Study', iStudy);
        iDataLocal = iDatas(iDataStudies == iStudy);
        % Add files to list
        DataFiles = cat(2, DataFiles, {sStudy.Data(iDataLocal).FileName});
        % Raw data
        isRaw = any(strcmpi({sStudy.Data(iDataLocal).DataType}, 'raw'));
    end
end
% Default file tag
if isDataCov
    strComment = 'Data covariance';
else
    strComment = 'Noise covariance';
end

%% ===== GET DATA CHANNELS =====
% Get channel studies
sTargetStudies = bst_get('Study', iTargetStudies);
% Find a study with channel file
iWithChan = find(~cellfun(@isempty, {sTargetStudies.Channel}), 1);
if isempty(iWithChan)
    error('No channel file.');
end
% Get one channel study
sStudy = sTargetStudies(iWithChan);
% Load channel file
ChannelMat = in_bst_channel(sStudy.Channel.FileName);
% Get all the valid channels
iChan = good_channel(ChannelMat.Channel, [], {'MEG','EEG','ECOG','SEEG'});
nChanAll = length(ChannelMat.Channel);
% Get the possible channe types
ChannelTypes = unique({ChannelMat.Channel(iChan).Type});

%% ===== READ ALL TIME VECTORS =====
% Progress bar
bst_progress('start', 'Read recordings information', 'Analysing input files...', 0, length(DataFiles));
% Regular list of imported data files
if ~isRaw
    nFiles = length(DataFiles);
    DataMats = repmat(struct('Time', [], 'SamplingRate', [], 'nAvg', [], 'Leff', [], 'iEpoch', [], 'iBadTime', []), nFiles);
    % Loop on all input files
    for iFile = 1:length(DataFiles)
        % Load file metadata
        DataMat = in_bst_data(DataFiles{iFile}, 'Time', 'nAvg', 'Leff');
        % Check file time
        if (length(DataMat.Time) < 3)
            error(['File has no time dimension: ' DataFiles{iFile}]);
        end
        % Check sampling rate
        if (iFile == 1)
            SamplingRate = DataMat.Time(2) - DataMat.Time(1);
        elseif (abs(SamplingRate - (DataMat.Time(2) - DataMat.Time(1))) > 1e-6)
            error(['The files you selected have different sampling frequencies. They should not be processed together.' 10 ...
                   'Please only select recordings with the same sampling frequency.']);
        end
        % Save values
        DataMats(iFile).Time = DataMat.Time;
        DataMats(iFile).nAvg = DataMat.nAvg;
        DataMats(iFile).Leff = DataMat.Leff;
        bst_progress('inc', 1);
    end
% Raw file
else
    % Only one raw file allowed
    if (length(DataFiles) > 1)
        error('Only one raw file allowed for covariance computation');
    end
    nFiles = 1;
    % Read the description of the raw file
    RawMat = in_bst_data(DataFiles{1});
    sFile = RawMat.F;
    clear RawMat;
    % Get bad segments/epochs
    [badSeg, badEpochs] = panel_record('GetBadSegments', sFile);

    % Initialize DataMats structure
    DataMats = repmat(struct('Time', [], 'nAvg', [], 'Leff', [], 'iEpoch', [], 'iBadTime', []), 0);
    % Define size of blocks to read
    MAX_BLOCK_SIZE = 10000;
    % Loop on epochs
    for iEpoch = 1:max(length(sFile.epochs), 1)
        % Bad epoch
        if ~isempty(sFile.epochs) && sFile.epochs(iEpoch).bad
            disp(sprintf('NOISECOV> Ignoring epoch #%d (tagged as bad)', iEpoch));
        end
        % Get the bad segments for this epoch
        iBadEpoch = find(badEpochs == iEpoch);
        if ~isempty(iBadEpoch)
            badSegEpoc = badSeg(:, iBadEpoch);
        else
            badSegEpoc = [];
        end
        % Get total number of samples
        if ~isempty(sFile.epochs)
            samples = round(sFile.epochs(iEpoch).times .* sFile.prop.sfreq);
            nAvg = double(sFile.epochs(iEpoch).nAvg);
        else
            samples = round(sFile.prop.times .* sFile.prop.sfreq);
            nAvg = 1;
        end
        totalSmpLength = double(samples(2) - samples(1)) + 1;
        % Number of blocks to split this epoch in
        nbBlocks = ceil(totalSmpLength / MAX_BLOCK_SIZE);
        % For each block
        for iBlock = 1:nbBlocks
            % Get samples indices for this block (start ind = 0)
            smpBlock = samples(1) + [(iBlock - 1) * MAX_BLOCK_SIZE, min(iBlock * MAX_BLOCK_SIZE - 1, totalSmpLength - 1)];
            smpList = smpBlock(1):smpBlock(2);
            % Create a data block
            iNew = length(DataMats) + 1;
            DataMats(iNew).iEpoch = iEpoch;
            DataMats(iNew).Time   = smpList ./ sFile.prop.sfreq;
            DataMats(iNew).nAvg   = nAvg;
            DataMats(iNew).Leff   = nAvg;
            % Remove the portions that have bad segments in them
            iBadTime = [];
            for ix = 1:size(badSegEpoc, 2)
                iBadTime = [iBadTime, find((smpList >= badSegEpoc(1,ix)) & (smpList <= badSegEpoc(2,ix)))];
            end
            if ~isempty(iBadTime)
                DataMats(iNew).iBadTime = iBadTime;
            end
        end
    end
    SamplingRate = 1 ./ sFile.prop.sfreq;
end
% Get number of samples and sampling rates
nSamples = length([DataMats.Time]) - length([DataMats.iBadTime]);
% Close progress bar
bst_progress('stop');
% Check number of actual samples available for computation
if (nSamples == 0)
    error('This selection does not contain any file that can be used for computing the covariance matrix.');
end

%% ===== GET NOISECOV OPTIONS ======
% Get number of files
nBlocks = length(DataMats);
% If the options were not passed in argument
if isempty(Options)
    % Loop to get all the valid times
    allTimes = [];
    for iFile = 1:length(DataMats)
        tmpTime = DataMats(iFile).Time;
        tmpTime(DataMats(iFile).iBadTime) = [];
        allTimes = [allTimes, tmpTime];
    end
    % Prepare GUI options
    guiOptions.timeSamples  = sort(allTimes);
    guiOptions.nFiles       = nFiles;
    guiOptions.nBlocks      = nBlocks;
    guiOptions.nChannels    = length(iChan);
    guiOptions.freq         = 1 ./ SamplingRate;
    guiOptions.isDataCov    = isDataCov;
    guiOptions.ChannelTypes = ChannelTypes;
    % Display dialog window
    Options = gui_show_dialog(strComment, @panel_noisecov, 1, [], guiOptions);
    if isempty(Options)
        return
    end
end
% Get only the channels of the selected modality
if ~isempty(Options.ChannelTypes)
    iChan = good_channel(ChannelMat.Channel, [], Options.ChannelTypes);
end
if isempty(iChan)
    error('No channels are selected from this file.');
end

%% ===== COMPUTE AVERAGE/TIME =====
if strcmpi(Options.RemoveDcOffset, 'all')
    % Compute the average across ALL the time samples of ALL the files
    Favg = zeros(nChanAll, 1);
    Ntotal = zeros(nChanAll, 1);
    % Progress bar
    bst_progress('start', 'Average across time', 'Computing average across time...', 0, nBlocks);
    % Loop on all the files
    for iFile = 1:nBlocks
        bst_progress('inc', 1);
        % Load recordings
        [DataMat, iTimeBaseline] = ReadRecordings();
        if isempty(iTimeBaseline)
            continue
        end
        % Get good channels
        iGoodChan = intersect(find(DataMat.ChannelFlag == 1), iChan);

        % === Compute average ===
        % Favg(iGoodChan)   = Favg(iGoodChan)   + double(DataMat.nAvg) .* sum(DataMat.F(iGoodChan,iTimeBaseline),2);
        % Ntotal(iGoodChan) = Ntotal(iGoodChan) + double(DataMat.nAvg) .* length(iTimeBaseline);
        Favg(iGoodChan)   = Favg(iGoodChan)   + double(DataMat.Leff) .* sum(DataMat.F(iGoodChan,iTimeBaseline),2);
        Ntotal(iGoodChan) = Ntotal(iGoodChan) + double(DataMat.Leff) .* length(iTimeBaseline);
    end
    % Remove zero-values in Ntotal
    Ntotal(Ntotal == 0) = 1;
    % Divide each channel by total number of time samples
    Favg = Favg ./ Ntotal;
end

%% ===== COMPUTE NOISE COVARIANCE =====
Ntotal   = zeros(nChanAll);
NoiseCov = zeros(nChanAll);
FourthMoment = zeros(nChanAll);
% Progress bar
bst_progress('start', strComment, ['Computing ' lower(strComment) '...'], 0, nBlocks);
drawnow
% Loop on all the files
for iFile = 1:nBlocks
    bst_progress('inc', 1);
    % Load recordings
    [DataMat, iTimeBaseline, iTimeCov] = ReadRecordings();
    if isempty(iTimeBaseline)
        continue
    end
    N = length(iTimeCov);

    % === Compute average ===
    if strcmpi(Options.RemoveDcOffset, 'file')
        % Get good channels
        iGoodChan = intersect(find(DataMat.ChannelFlag == 1), iChan);
        % Average baseline values
        Favg = mean(DataMat.F(:,iTimeBaseline), 2);
    end

    % === Compute covariance ===
    % Remove average
    DataMat.F(iGoodChan,:) = bst_bsxfun(@minus, DataMat.F(iGoodChan,:), Favg(iGoodChan,1));
    % Compute covariance for this file
    % fileCov  = DataMat.nAvg .* (DataMat.F(iGoodChan,iTimeCov)    * DataMat.F(iGoodChan,iTimeCov)'   );
    % fileCov2 = DataMat.nAvg .* (DataMat.F(iGoodChan,iTimeCov).^2 * DataMat.F(iGoodChan,iTimeCov)'.^2);
    fileCov  = DataMat.Leff .* (DataMat.F(iGoodChan,iTimeCov)    * DataMat.F(iGoodChan,iTimeCov)'   );
    fileCov2 = DataMat.Leff .* (DataMat.F(iGoodChan,iTimeCov).^2 * DataMat.F(iGoodChan,iTimeCov)'.^2);
    % Add file covariance to accumulator
    NoiseCov(iGoodChan,iGoodChan)     = NoiseCov(iGoodChan,iGoodChan)     + fileCov;
    FourthMoment(iGoodChan,iGoodChan) = FourthMoment(iGoodChan,iGoodChan) + fileCov2;
    Ntotal(iGoodChan,iGoodChan) = Ntotal(iGoodChan,iGoodChan) + N;
end
% Remove zeros from N matrix
Ntotal(Ntotal <= 1) = 2;
% Divide final matrix by number of samples
NoiseCov     = NoiseCov     ./ (Ntotal - 1);
FourthMoment = FourthMoment ./ (Ntotal - 1);
% Display result in the command window
nSamplesTotal = max(Ntotal(:));
% disp(['Number of time samples used for the noise covariance: ' num2str(nSamplesTotal)]);
% Check for NaN values
if (nnz(isnan(NoiseCov)) > 0)
    error('The output covariance contains NaN values. Please check your recordings and tag the bad channels correctly.');
end

%% ===== IMPORTING IN DATABASE =====
% Build file structure
NoiseCovMat = db_template('noisecovmat');
NoiseCovMat.NoiseCov     = NoiseCov;
NoiseCovMat.Comment      = [strComment ': '];
NoiseCovMat.nSamples     = Ntotal;
NoiseCovMat.FourthMoment = FourthMoment;
% Add names of sensors
allTypes = unique({ChannelMat.Channel(iChan).Type});
if all(ismember({'MEG MAG', 'MEG GRAD'}, allTypes))
    allTypes = setdiff(allTypes, {'MEG MAG', 'MEG GRAD'});
    allTypes = union(allTypes, {'MEG'});
end
for i = 1:length(allTypes)
    NoiseCovMat.Comment = [NoiseCovMat.Comment, allTypes{i}];
    if (i < length(allTypes))
        NoiseCovMat.Comment = [NoiseCovMat.Comment, ', '];
    end
end
% Add history entry
if isDataCov
    if isempty(Options.DataTimeWindow)
        strTime = 'Data=[All file], ';
    elseif (max(abs(Options.DataTimeWindow)) > 2)
        strTime = sprintf('Data=[%1.3f, %1.3f]s, ', Options.DataTimeWindow);
    else
        strTime = sprintf('Data=[%d, %d]ms, ', round(Options.DataTimeWindow * 1000));
    end
    if isempty(Options.Baseline)
        strTime = [strTime, 'Baseline=[All file]'];
    elseif (max(abs(Options.Baseline)) > 2)
        strTime = [strTime, sprintf('Baseline=[%1.3f, %1.3f]s', Options.Baseline)];
    else
        strTime = [strTime, sprintf('Baseline=[%d, %d]ms', round(Options.Baseline * 1000))];
    end
else
    if isempty(Options.Baseline)
        strTime = '[All file]';
    elseif (max(abs(Options.Baseline)) > 2)
        strTime = sprintf('[%1.3f, %1.3f]s', Options.Baseline);
    else
        strTime = sprintf('[%d, %d]ms', round(Options.Baseline * 1000));
    end
end
NoiseCovMat = bst_history('add', NoiseCovMat, 'compute', sprintf('Computed based on %d files (%d blocks, %d samples): %s, %s', nFiles, nBlocks, nSamplesTotal, strTime, Options.RemoveDcOffset));
% Add list of input files in history
for iSrc = 1:length(DataFiles)
    NoiseCovMat = bst_history('add', NoiseCovMat, 'src', DataFiles{iSrc});
end
% Save in database
NoiseCovFiles = import_noisecov(iTargetStudies, NoiseCovMat, Options.ReplaceFile, isDataCov);
% Close progress bar
bst_progress('stop');

%% ========================================================================
%  ======= SUPPORT FUNCTIONS ==============================================
%  ========================================================================

%% ===== READ RECORDINGS BLOCK =====
    function [DataMat, iTimeBaseline, iTimeCov] = ReadRecordings()
        iTimeBaseline = [];
        iTimeCov = [];
        if ~isRaw
            bst_progress('text', ['File: ' DataFiles{iFile}]);
            DataMat = in_bst_data(DataFiles{iFile}, 'F', 'ChannelFlag', 'Time', 'nAvg', 'Leff');
        else
            DataMat = DataMats(iFile);
            % If file is block does not contain any baseline segment: skip it
            if (length(DataMats(iFile).Time) < 2) || (~isempty(Options.Baseline) && ((DataMats(iFile).Time(end) < Options.Baseline(1)) || (DataMats(iFile).Time(1) > Options.Baseline(2))))
                return;
            end
            % Read raw block
            UseSsp = 1;
            [DataMat.F, DataMat.Time] = panel_record('ReadRawBlock', sFile, ChannelMat, DataMats(iFile).iEpoch, DataMats(iFile).Time([1,end]), 0, 1, 'no', UseSsp);
            DataMat.ChannelFlag = sFile.channelflag;
        end
        % WE DON'T WANT TO APPLY THE AVERAGE REFERENCE AT THIS STAGE: NOISE COV IS CALCULATED ON ORIGINAL MONTAGE, AND AVG REF IS CALCULATED IN PROCESS_INVERSE.
%         % Apply average reference: separately SEEG, ECOG, EEG
%         if any(ismember(unique({ChannelMat.Channel.Type}), {'EEG','ECOG','SEEG'}))
%             sMontage = panel_montage('GetMontageAvgRef', ChannelMat.Channel, DataMat.ChannelFlag);
%             DataMat.F = sMontage.Matrix * DataMat.F;
%         end
        % If not enough time frames (ie. if data files)
        if (length(DataMat.Time) <= 2) || (size(DataMat.F,2) <= 2)
            return;
        end
        % Check size
        if (size(DataMat.F,1) ~= nChanAll)
            error('Number of channels is not constant.');
        end
        % Get the times required in the block (only when there are bad segments in raw files)
        if isRaw && ~isempty(DataMat.iBadTime)
            DataMat.Time(DataMat.iBadTime) = [];
            DataMat.F(:, DataMat.iBadTime) = [];
        end
        % Get times that are considered as baseline
        if isempty(DataMat.Time)
            iTimeBaseline = [];
        elseif ~isempty(Options.Baseline)
            iTimeBaseline = panel_time('GetTimeIndices', DataMat.Time, Options.Baseline);
        else
            iTimeBaseline = 1:length(DataMat.Time);
        end
        % Get the time indices on which to compute the covariance
        if isDataCov
            if isempty(DataMat.Time)
                iTimeCov = [];
            elseif ~isempty(Options.DataTimeWindow)
                iTimeCov = panel_time('GetTimeIndices', DataMat.Time, Options.DataTimeWindow);
            else
                iTimeCov = 1:length(DataMat.Time);
            end
        else
            iTimeCov = iTimeBaseline;
        end
    end
end


function view = stimPlusTSeriesMovie(view,stim,scans,slices,varargin);
%  view = stimPlusTSeriesMovie(view,stim,[scans,slices,options]);
%
% Similar to makeTSeriesMovie, makes a movie with the tSeries
% overlayed over anatomies, but with some additional features:
%
%   1) Shows next to the movie the stimuli that were showing
%   at each frame;
%
%   2) Has a simple interface allowing the threshold to be
%   set for what (normalized) values of the tSeries are
%   overlayed over the anatomies.
%
% stim must be a struct containing the following fields:
%   images: cell specifying which images were shown. Each entry
%           in the cell can be a matrix containing the image, or
%           a string specifying the path to load the image;
%           [If left empty, will try to load these from the third
%           column of the parfiles assigned to the selected scans]
%   frames: array specifying which frames from the tSeries to use
%           [default is 1:numFrames, but if, e.g., many images appear
%           within one TR you may 'double up' those entries of the
%           frames field]
%   order:  array specifying which image (index in the images cell)
%           to show for each frame. Should be the same length as
%           the frames field. [If empty, will use the conditions
%           specified in the parfiles assigned to the selected scans]
%
%
% Defaults to the current scan and slices, though multiple
% scans/slices may be specified.
%
%
% 05/04 ras.

% the callbacks from the UI controls give as the
% first argument a string specifying the sort of
% callback. Check:
if ischar(view)
    % it's a callback, not a call to open a new movie
    switch view
        case 'play', playStimMovie; return;
        case 'rebuild', rebuildStimMovie;
        case 'export', exportStimMovie; return;
        otherwise, return;
    end
elseif ~isstruct(view)
    % if it's not a struct, something's wrong
    help stimPlusTSeriesMovie;
    error('stimPlusTSeriesMovie called wrong.');
end

% --------- Everything below is code to build the movie ----------- %

if ieNotDefined('scans')    scans = viewGet(view,'curScan');   end
if ieNotDefined('slices')   slices = viewGet(view,'curSlice'); end

%%%%% default params
thresh = 0.6;       % normalized func intensity val
detrend = 0;        % detrending takes a long time on a whole tSeries
cmap = hot(256);    % color map for tSeries overlay
movieRate = 6;     % rate in frames per second to play movie
TR = viewGet(view,'TR');

%%%%% parse options
% put code here

% check that the stim struct has the proper fields
if ~isfield(stim,'images') | ~isfield(stim,'frames') | ~isfield(stim,'order')
    help stimPlusTSeriesMovie;
    error('Second argument does not have proper fields.');
end

% allow for some fields to be empty; this means get the information
% from the parfiles:
if isempty(stim.images)
    trials = exp5_concatParfiles(view,scans);
    stim.images = trials.image;
end

if isempty(stim.frames)
    global dataTYPES;
    dt = viewGet(view,'curdt');
    nFrames = 0;
    for s = scans
        nFrames = nFrames + dataTYPES(dt).scanParams(s).nFrames;
    end
    stim.frames = 1:nFrames;
end

if isempty(stim.order)
   % assume the conditions in the parfiles point to the index
   % of which image to use in trials.images -- interpolate
   % to make order a vector showing which condition at each frame
   trials = exp5_concatParfiles(view,scans);
   ok = find(trials.onset < nFrames);
   stim.order = NaN*ones(1,nFrames);
   stim.order(trials.onset(ok)) = trials.cond(ok);
   % we've set the onset frames of each cond; now fill in the intervals
   fillin = find(isnan(stim.order));
   for j = fillin
       lastOnset = find((1:nFrames < j) & ~isnan(stim.order));
       stim.order(j) = stim.order(lastOnset(end));
   end
end

% load any images specified by paths in stim.images
for i = 1:length(stim.images)
    if ischar(stim.images{i})
        if exist(stim.images{i},'file')
            stim.images{i} = imread(stim.images{i},'jpg');
        else
            error(sprintf('File %s not found.',stim.images{i}));
        end
    end
end

% open the movie window; add handles to stim struct
stim.cmap = cmap;
stim = openMovieWin(view,stim,scans,slices);

% Get, rescale anatomy image
anatIm = makeMontage(view.anat,slices);
anatClip = viewGet(view,'anatClip');
minVal = double(min(anatIm(:)));
maxVal = double(max(anatIm(:)));
anatClipMin = min(anatClip)*(maxVal-minVal) + minVal;
anatClipMax = max(anatClip)*(maxVal-minVal) + minVal;
anatIm = (rescale2(double(anatIm),[anatClipMin,anatClipMax],[1,256]));
anatIm = normalize(anatIm,0,1);
imSize = size(anatIm);

% make all the stimulus images squares with the edge equal to
% the y-size of the fnctional image (this will ensure everything
% stays the same relative size):
for i = 1:length(stim.images)
    if ~isequal(size(stim.images{i}),[imSize(1) imSize(1)])
        stim.images{i} = imresize(stim.images{i},[imSize(1) imSize(1)]);
    end
end

% make a spacer b/w the functional img and stimulus img
spacer = uint8(205*ones(imSize(1),50,3));

% load the tSeries for the selected scans and slices
fprintf('Loading tSeries...');
for s = 1:length(scans)
    for i = 1:length(slices)
        tS = loadtSeries(view,scans(s),slices(i));
        if s==1
            tSeriesAll{i} = tS;
        else
            tSeriesAll{i} = [tSeriesAll{i} tS];
        end
    end
end
fprintf('done.\n');

% ----- build the movie ----- %
nFrames = length(stim.frames);
dims = sliceDims(view,scans(1));
for f = 1:nFrames

    % construct the functional img (anat + overlay) for this frame
    func = zeros(imSize(1),imSize(2),3); % truecolor image
    clear tSeries;
    for ii = 1:length(slices)
        tSeries(:,:,ii) = reshape(tSeriesAll{ii}(f,:),dims);
    end
    tSeries = makeMontage(tSeries);
    overlay = upSampleRep(tSeries,size(anatIm));
    overlay = normalize(overlay,0,1);
    mask = find(overlay >= thresh);
    overlay(mask) = normalize(overlay(mask),1,256); % indices into cmap
    overlay = round(overlay);
    for col = 1:3
        tmp = anatIm;
        tmp(mask) = cmap(overlay(mask),col);
        func(:,:,col) = tmp;
    end
    func = uint8(256 .* func);

    % get stimulus img for this frame
    whichStim = stim.order(f);
    if whichStim==0  % null condition, maybe a better way to do this
        stimulus = zeros(imSize(1),imSize(1));
    else
        stimulus = stim.images{whichStim};
    end
    stimulus = repmat(stimulus,[1 1 3]);
    stimulus = uint8(stimulus);

    % merge the images together, display and grab a movie frame
    axes(stim.handles.axes);
    img = [stimulus spacer func];
    imshow(img);
    htmp = text(20,20,num2str(f)); set(htmp,'Color','r');
    title(sprintf('%i secs',f*TR),'FontSize',16);
    stim.movie(f) = getframe;

    % might also be good to have this be attached to
    % the view like makeTSeriesMovie, but I have to
    % do some other things to ensure full compatibility:
    view.ui.movie.movie(f) = getframe;
end

colorbar horiz;
colormap(stim.cmap);

% set some important params on the stim struct; stash in fig's UserData
stim.viewName = view.name;
stim.thresh = thresh;
stim.detrend = detrend;
stim.movieRate = movieRate;
set(stim.handles.figure,'UserData',stim);

return
% /----------------------------------------------------------------------/ %

% /----------------------------------------------------------------------/ %
function stim = openMovieWin(view,stim,scans,slices);
% opens the movie window and adds the handles
stim.handles.figure = figure('Units','Normalized','Position',[.2 .2 .6 .6]);
set(gcf,'Name',['tSeries movie, scan ',num2str(scans),', slices ',num2str(slices)]);
% set(gcf,'DefaultTextColor','blue');
stim.handles.axes = axes('Position',[.05 .3 .75 .6]);
cbstr = 's = get(gcf,''UserData''); movie(s.movie,1,s.movieRate);';
stim.handles.play = uicontrol('Style','pushbutton','Units','Normalized',...
                              'Position',[.83 .7 .12 .08],'String','Play',...
                              'Callback',cbstr);
stim.handles.rebuild = uicontrol('Style','pushbutton','Units','Normalized',...
                              'Position',[.83 .5 .12 .08],'String','Rebuild',...
                              'Callback','stimPlusTSeriesMovie(''rebuild'');');
stim.handles.export = uicontrol('Style','pushbutton','Units','Normalized',...
                              'Position',[.83 .3 .12 .08],'String','Export',...
                              'Callback','stimPlusTSeriesMovie(''export'');');
return
% /----------------------------------------------------------------------/ %

% /----------------------------------------------------------------------/ %
function playStimMovie(nTimes);
% plays the movie once it's made.
if ieNotDefined('nTimes')   nTimes = 1;   end
stim = get(gcf,'UserData');
movie(stim.movie,nTimes,stim.movieRate);
return
% /----------------------------------------------------------------------/ %

% /----------------------------------------------------------------------/ %
function rebuildStimMovie;
% rebuild the stimulus movie, with user-modified params.
return
% /----------------------------------------------------------------------/ %

% /----------------------------------------------------------------------/ %
function exportStimMovie;
% export the movie to an AVI file.
if isunix
    compression = 'None';
else
    compression = 'Indeo5';
end
stim = get(gcf,'UserData');
[fname, pth] = myUiPutFile(pwd,'*.avi','Name your saved movie...');
savePath = fullfile(pth,fname);
movie2avi(stim.movie,savePath,'FPS',stim.movieRate,'Compression',compression);
fprintf('Saved %s.\n',savePath);
return


%% NISwGSP-denny
% %{
imfolder = 'images\NISwGSP-denny';
im_n = 15;
imfile = cell(im_n,1);
for ii = 1:im_n
    imfile{ii} = sprintf('%s\\denny%02d.jpg', imfolder, ii-1);
end

im = cell(im_n,1);
for ii = 1:im_n
    im{ii} = imread(imfile{ii});
end

edge_list = zeros(im_n,2);
ei = 0;
for ii = 1:im_n-1
    ei = ei + 1;
    edge_list(ei,:) = [ii,ii+1];
end
edge_list(im_n,:) = [im_n,1];

imsize = zeros(im_n,3);

for ii = 1:im_n
    imsize(ii,:) = size(im{ii});
    if imsize(ii,1) > 720
        scale = 720/size(im{ii}, 1);
        im{ii} = imresize(im{ii}, scale);

        imsize(ii,:) = size(im{ii});
    end
end

mosaic = REW_mosaic( im, edge_list, 0, 'equi', 0.02, imfolder );
%}


%   AUTHORSHIP
%   Primary Developer: Stephen Meehan <swmeehan@stanford.edu>
%   Math Lead & Secondary Developer:  Connor Meehan <connor.gw.meehan@gmail.com>
%   Bioinformatics Lead:  Wayne Moore <wmoore@stanford.edu>
%   Provided by the Herzenberg Lab at Stanford University
%   License: BSD 3 clause
%
classdef CellBasics
    methods(Static)
        function tabLines=ToTabLines(data, labels, removeHtml)
            if nargin<3
                removeHtml=true;
            end
            if removeHtml
                J=edu.stanford.facs.swing.Basics;
            end
            tabLines=java.util.ArrayList;
            [rows,cols]=size(data);
            if nargin>1
                data=[labels; data];
            end
            for row=1:rows
                line='';
                for col=1:cols
                    value=data{row, col};
                    if removeHtml && ischar(value)
                        value=J.RemoveXml(value);
                    end
                    if isnumeric(value)
                        value=num2str(value);
                    end
                    line=sprintf('%s%s\t', line, value);
                end
                tabLines.add(line);
            end
        end

        function groups=GetRowGroups(sortedRows)
            groups={};
            [R,C]=size(sortedRows);
            if R>0
                lastStartingR=1;
                for r=2:R
                    if sortedRows(r,1)>sortedRows(r-1,1)+1
                        groups{end+1}=sortedRows(lastStartingR:r-1,:);
                        lastStartingR=r;
                    end
                end
            end
            groups{end+1}=sortedRows(lastStartingR:r,:);
        end

        function rowIdxs=Find(c, colIdxs, fnc, first, rowStartIdx)
            if nargin<5
                rowStartIdx=1;
                if nargin<4
                    first=true;
                end
            end
            N=size(c,1);
            C=size(c,2);
            colIdxsN=length(colIdxs);
            rowIdxs=[];
            for i=rowStartIdx:N
                for j=1:colIdxsN
                    colIdx=colIdxs(j);
                    if colIdx>=1 && colIdx<=C
                        o=c{i,colIdx};
                        if fnc(o, i, colIdx)
                            if colIdxsN==1
                                rowIdxs(end+1)=i;
                            else
                                rowIdxs(end+1,:)=[i colIdx];
                            end
                            if first
                                return;
                            end
                        end
                    end
                end
            end
        end

        function yes=equals(o, arg)
            yes=o==arg;
        end

        function [found, idx]=Contains(cell, object)
            found=false;
            N_=length(cell);
            for idx=1:N_
                if isequal(object, cell{idx})
                    found=true;
                    return;
                end
            end
            idx=0;
        end

        function c=Java(collectionOrArray)
            try
                N=collectionOrArray.size;
                c=cell(1,N);
                it=collectionOrArray.iterator;
                for i=1:N
                    c{i}=it.next;
                end
            catch
                N=length(collectionOrArray);
                c=cell(1,N);
                for i=1:N
                    c{i}=collectionOrArray(i);
                end

            end
        end

        function u=UniqueNumbers(cellNums)
            if isnumeric(cellNums)
                u=unique(cellNums);
            else
                u=[];
                N=length(cellNums);
                for i=1:N
                    u=unique([u cellNums{i}']);
                end
            end
        end
    end
end