function resu = cat(dim, A, varargin)
  %# function resu = cat(dim, A, varargin)
  %# This is the concatenation operator for a dataframe object. "Dim"
  %# has the same meaning as ordinary cat. Next arguments may be
  %# dataframe, vector/matrix, or two elements cells. First one is taken
  %# as row/column name, second as data.

  %% Copyright (C) 2009-2015 Pascal Dupuis <cdemills@gmail.com>
  %%
  %% This file is part of Octave.
  %%
  %% Octave is free software; you can redistribute it and/or
  %% modify it under the terms of the GNU General Public
  %% License as published by the Free Software Foundation;
  %% either version 2, or (at your option) any later version.
  %%
  %% Octave is distributed in the hope that it will be useful,
  %% but WITHOUT ANY WARRANTY; without even the implied
  %% warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  %% PURPOSE.  See the GNU General Public License for more
  %% details.
  %%
  %% You should have received a copy of the GNU General Public
  %% License along with Octave; see the file COPYING.  If not,
  %% write to the Free Software Foundation, 51 Franklin Street -
  %% Fifth Floor, Boston, MA 02110-1301, USA.
  
  %#
  %# $Id$
  %#

  if (~isa (A, 'dataframe')),
    A = dataframe (A);
  endif

  switch dim
    case 1
      resu = A;
          
      for indi = (1:length (varargin))
        B = varargin{indi};
        if (~isa (B, 'dataframe'))
          if (iscell (B) && 2 == length (B))
            B = dataframe (B{2}, 'rownames', B{1});
          else
            B = dataframe (B, 'colnames', inputname(2+indi));
          endif
        endif
        if (resu._cnt(2) ~= B._cnt(2))
          error ('Different number of columns in dataframes');
        endif
        %# do not duplicate empty names
        if (~isempty (resu._name{1}) || ~isempty (B._name{1}))
          if (length (resu._name{1}) < resu._cnt(1))
            resu._name{1}(end+1:resu._cnt(1), 1) = {''};
          endif
          if (length (B._name{1}) < B._cnt(1))
            B._name{1}(end+1:B._cnt(1), 1) = {''};
          endif
          resu._name{1} = vertcat (resu._name{1}(:),  B._name{1}(:));
          resu._over{1} = [resu._over{1} B._over{1}];
        endif
        resu._cnt(1) = resu._cnt(1) + B._cnt(1);
        if (size (resu._ridx, 2) < size (B._ridx, 2))
          resu._ridx(:, end+1:size(B._ridx, 2)) = NA;
        elseif (size (resu._ridx, 2) > size (B._ridx, 2))
          B._ridx(:, end+1:size(resu._ridx, 2)) = NA;
        endif
        resu._ridx = [resu._ridx; B._ridx];
        %# find data with same column names
        dummy = A._over{2} & B._over{2}; 
        indA = true (1, resu._cnt(2));
        indB = true (1, resu._cnt(2));
        for indj = (1:resu._cnt(2))
          if (dummy(indj))
            indk = strmatch (resu._name{2}(indj), B._name{2}, 'exact');
            if (~isempty (indk))
              indk = indk(1);
              if (~strcmp (resu._type{indj}, B._type{indk}))
                error ("Trying to mix columns of different types");
              endif
            endif
          else
            indk = indj;
          endif
          resu._data{indj} = [resu._data{indj}; B._data{indk}];
          indA(indj) = false; indB(indk) = false;
        endfor
        if (any (indA) || any (indB))
          error ('Different number/names of columns in dataframe');
        endif
        
      endfor
      
    case 2
      resu = A;

      for indi = (1:length (varargin))
        B = varargin{indi};
        if (~isa (B, 'dataframe'))
          if (iscell (B) && 2 == length (B))
            B = dataframe (B{2}, 'colnames', B{1});
          else
            B = dataframe (B, 'colnames', inputname(2+indi));
          endif
          B._ridx = resu._ridx; %# make them compatibles
        endif
        if (resu._cnt(1) ~= B._cnt(1))
          error ('Different number of rows in dataframes');
        endif
        if (any(resu._ridx(:) - B._ridx(:)))
          error ('dataframes row indexes not matched');
        endif
        resu._name{2} = vertcat (resu._name{2}, B._name{2});
        resu._over{2} = [resu._over{2} B._over{2}];
        resu._data(resu._cnt(2)+(1:B._cnt(2))) = B._data;
        resu._type(resu._cnt(2)+(1:B._cnt(2))) = B._type;
        resu._cnt(2) = resu._cnt(2) + B._cnt(2);        
      endfor
      
    case 3
      resu = A;
      
      for indi = (1:length (varargin))
        B = varargin{indi};
        if (~isa (B, 'dataframe'))
          if (iscell (B) && 2 == length (B)),
            B = dataframe (B{2}, 'rownames', B{1});
          else
            B = dataframe (B, 'colnames', inputname(indi+2)); 
          endif
        endif
        if (resu._cnt(1) ~= B._cnt(1))
          error ('Different number of rows in dataframes');
        endif
        if (resu._cnt(2) ~= B._cnt(2)),
          error ('Different number of columns in dataframes');
        endif
        %# to be merged against 3rd dim, rownames must be equals, if
        %# non-empty. Columns are merged based upon their name; columns
        %# with identic content are kept.

        if (size(resu._ridx, 2) < size(B._ridx, 2))
          resu._ridx(:, end+1:size(B._ridx, 2)) = NA;
        elseif (size(resu._ridx, 2) > size(B._ridx, 2))
          B._ridx(:, end+1:size(resu._ridx, 2)) = NA;
        endif
        resu._ridx = cat (3, resu._ridx, B._ridx);
        %# find data with same column names
        indA = true (1, resu._cnt(2));
        indB = true (1, resu._cnt(2));
        dummy = A._over{2} & B._over{2}; 
        for indj = (1:resu._cnt(2))
          if (dummy(indj))
            indk = strmatch (resu._name{2}(indj), B._name{2}, 'exact');
            if (~isempty (indk)),
              indk = indk(1);
              if (~strcmp (resu._type{indj}, B._type{indk})),
                error("Trying to mix columns of different types");
              endif
            endif
          else
            indk = indj;
          endif
          if (all ([isnumeric(resu._data{indj}) isnumeric(B._data{indk})])),
            %# iterate over the columns of resu and B
            op1 = resu._data{indj}; op2 = B._data{indk};
            for ind2 = (1:columns (op2))
              indr = false;
              for ind1 = (1:columns (op1))
                if (all (abs (op1(:, ind1) - op2(:, ind2)) <= eps)),
                  resu._rep{indj} = [resu._rep{indj} ind1];
                  indr = true;
                  break;
                endif
              endfor
              if (~indr),
                %# pad in the second dim
                resu._data{indj} = [resu._data{indj}, B._data{indk}];
                resu._rep{indj} = [resu._rep{indj} 1+length(resu._rep{indj})];
              endif
            endfor
          else
            resu._data{indj} = [resu._data{indj} B._data{indk}];
            resu._rep{indj} = [resu._rep{indj} 1+length(resu._rep({indj}))];
          endif
          indA(indj) = false; indB(indk) = false;
        endfor
        if (any (indA) || any (indB))
          error ('Different number/names of columns in dataframe');
        endif
      endfor
     
      resu = df_thirddim (resu);
      
    otherwise
      error ('Incorrect call to cat');
  endswitch
  
  %#  disp('End of cat'); keyboard
endfunction
