class ::Vector
  def to_matrix
    ::Matrix.columns([self.to_a])
  end
  def to_vector
    self
  end
end
class ::Matrix
  def to_matrix
    self
  end
  def to_dataset
    f = (self.respond_to? :fields_y) ? fields_y : column_size.times.map {|i| _("VAR_%d") % (i+1) }
    ds=Statsample::Dataset.new(f)
    f.each do |ff|
      ds[ff].type=:scale
      ds[ff].name=ff
    end
    row_size.times {|i|
      ds.add_case_array(self.row(i).to_a)
    }
    ds.update_valid_data
    ds.name=self.name if self.respond_to? :name
    ds
  end
  if defined? :eigenpairs
    alias_method :eigenpairs_ruby, :eigenpairs
  end
  
  if Statsample.has_gsl?
    # Optimize eigenpairs of extendmatrix module using gsl
    def eigenpairs
      to_gsl.eigenpairs
    end
  end
  
  def eigenvalues
    eigenpairs.collect {|v| v[0]}
  end
  def eigenvectors
    eigenpairs.collect {|v| v[1]}
  end
  def eigenvectors_matrix
    Matrix.columns(eigenvectors)
  end
  
  

  
  
  def to_gsl
    out=[]
    self.row_size.times{|i|
      out[i]=self.row(i).to_a
    }
    GSL::Matrix[*out]
  end
end

module GSL
  class Vector
    class Col
      def to_matrix
      ::Matrix.columns([self.size.times.map {|i| self[i]}])
      end
      def to_ary
        to_a
      end
      def to_gsl
        self
      end
    end
  end
  class Matrix
    def to_gsl
      self
    end
    
    def to_dataset
      f = (self.respond_to? :fields_y) ? fields_y : column_size.times.map {|i| _("VAR_%d") % (i+1) }
      ds=Statsample::Dataset.new(f)
      f.each do |ff|
        ds[ff].type=:scale
        ds[ff].name=ff
      end
      row_size.times {|i|
        ds.add_case_array(self.row(i).to_a)
      }
      ds.update_valid_data
      ds.name=self.name if self.respond_to? :name
      ds
    end
    
    def row_size
      size1
    end
    def column_size
      size2
    end
    def determinant
      det
    end
    def inverse
      GSL::Linalg::LU.invert(self)
    end
    def eigenvalues
      eigenpairs.collect {|v| v[0]}
    end
    def eigenvectors
      eigenpairs.collect {|v| v[1]}
    end
    
    # Matrix sum of squares
    def mssq
      sum=0
      to_v.each {|i| sum+=i**2}
      sum
    end
    
    def eigenvectors_matrix
      eigval, eigvec= GSL::Eigen.symmv(self)
      GSL::Eigen::symmv_sort(eigval, eigvec, GSL::Eigen::SORT_VAL_DESC)
      eigvec 
    end
    def eigenpairs
      eigval, eigvec= GSL::Eigen.symmv(self)
      GSL::Eigen::symmv_sort(eigval, eigvec, GSL::Eigen::SORT_VAL_DESC)
      @eigenpairs=eigval.size.times.map {|i|
        [eigval[i],eigvec.get_col(i)]
      }
    end
    
    #def eigenpairs_ruby
    #  self.to_matrix.eigenpairs_ruby
    #end
    def square?
      size1==size2
    end
    def to_matrix
      rows=self.size1
      cols=self.size2
      out=(0...rows).collect{|i| (0...cols).collect {|j| self[i,j]} }
      ::Matrix.rows(out)
    end
    def total_sum
      sum=0
      size1.times {|i|
        size2.times {|j|
          sum+=self[i,j]
        }
      }
      sum
    end
  end
end

module Statsample
  # Module to add names to X and Y fields
  module NamedMatrix
    include Summarizable  

    def fields
    raise "Should be square" if !square?
    fields_x
    end
    def fields=(v)
    raise "Matrix should be square" if !square?
    @fields_x=v
    @fields_y=v
    end
    def fields_x=(v)
    raise "Size of fields != row_size" if v.size!=row_size
    @fields_x=v
    end
    def fields_y=(v)
    raise "Size of fields != column_size" if v.size!=column_size
    @fields_y=v
    end
    def fields_x
    @fields_x||=row_size.times.collect {|i| _("X%d") % i} 
    end
    def fields_y
    @fields_y||=column_size.times.collect {|i| _("Y%d") % i} 
    end

    def name
      @name||=get_new_name
    end
    def name=(v)
      @name=v
    end
    def get_new_name
      @@named_matrix||=0
      @@named_matrix+=1
      _("Matrix %d") % @@named_matrix
    end
    
  end
  # Module to add method for variance/covariance and correlation matrices
  # == Usage
  #  matrix=Matrix[[1,2],[2,3]]
  #  matrix.extend CovariateMatrix
  # 
  module CovariateMatrix
    include NamedMatrix
    @@covariatematrix=0

    # Get type of covariate matrix. Could be :covariance or :correlation
    def _type
      if row_size==column_size
        if row_size.times.find {|i| self[i,i]!=1.0}
          :covariance
        else
          :correlation
        end
      else
        @type
      end
      
    end
    def _type=(t)
      @type=t
    end
    def correlation
      if(_type==:covariance)
        matrix=Matrix.rows(row_size.times.collect { |i|
          column_size.times.collect { |j|
            if i==j
              1.0
            else
              self[i,j].quo(Math::sqrt(self[i,i])*Math::sqrt(self[j,j]))
            end
          }
        })
        matrix.extend CovariateMatrix 
        matrix.fields_x=fields_x
        matrix.fields_y=fields_y
        matrix._type=:correlation
        matrix
      else
        self
      end
    end
    
    
    # Get variance for field k
    # 
    def variance(k)
      submatrix([k])[0,0]
    end
    
    def get_new_name
      @@covariatematrix+=1
      _("Covariate matrix %d") % @@covariatematrix
    end
    
    # Select a submatrix of factors. If you have a correlation matrix
    # with a, b and c, you could obtain a submatrix of correlations of
    # a and b, b and c or a and b
    #
    # You could use labels or index to select the factors.
    # If you don't specify columns, its will be equal to rows.
    #
    # Example:
    #   a=Matrix[[1.0, 0.3, 0.2],
    #            [0.3, 1.0, 0.5], 
    #            [0.2, 0.5, 1.0]]
    #   a.extend CovariateMatrix
    #   a.fields=%w{a b c}
    #   a.submatrix(%w{c a}, %w{b})
    #   => Matrix[[0.5],[0.3]]
    #   a.submatrix(%w{c a})
    #   => Matrix[[1.0, 0.2] , [0.2, 1.0]]
    def submatrix(rows,columns=nil)
      raise ArgumentError, "rows shouldn't be empty" if rows.respond_to? :size and rows.size==0
      columns||=rows
      # Convert all fields on index
      row_index=rows.collect {|v| 
        r=v.is_a?(Numeric) ? v : fields_x.index(v)
        raise "Index #{v} doesn't exists on matrix" if r.nil?
        r
      }
      column_index=columns.collect {|v| 
        r=v.is_a?(Numeric) ? v : fields_y.index(v)
        raise "Index #{v} doesn't exists on matrix" if r.nil?
        r
      }
      
      
      fx=row_index.collect {|v| fields_x[v]}
      fy=column_index.collect {|v| fields_y[v]}
        
      matrix= Matrix.rows(row_index.collect {|i|
        row=column_index.collect {|j| self[i,j]}})
      matrix.extend CovariateMatrix 
      matrix.fields_x=fx
      matrix.fields_y=fy
      matrix._type=_type
      matrix
    end
    def report_building(generator)
      @name||= (_type==:correlation ? _("Correlation"):_("Covariance"))+_(" Matrix")
      generator.table(:name=>@name, :header=>[""]+fields_y) do |t|
        row_size.times {|i|
          t.row([fields_x[i]]+row(i).to_a.collect {|i1|
              i1.nil? ? "--" : sprintf("%0.3f",i1).gsub("0.",".")
          })
        }
      end
    end
  end
end
