require 'extendmatrix'


class ::Matrix
  def to_matrix
    self
  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 Matrix
    def to_gsl
      self
    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
  end
end

module Statsample
  # Module to add method for variance/covariance and correlation matrices
  # == Usage
  #  matrix=Matrix[[1,2],[2,3]]
  #  matrix.extend CovariateMatrix
  # 
  module CovariateMatrix
    include Summarizable
    @@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
    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=(v)
      @name=v
    end
    def name
      @name||=get_new_name
    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.extends CovariateMatrix
    #   a.labels=%w{a b c}
    #   a.submatrix(%{c a}, %w{b})
    #   => Matrix[[0.5],[0.3]]
    #   a.submatrix(%{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 labels on index
      row_index=rows.collect {|v| 
        v.is_a?(Numeric) ? v : fields_x.index(v)
      }
      column_index=columns.collect {|v| 
        v.is_a?(Numeric) ? v : fields_y.index(v)
      }

      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]]+@rows[i].collect {|i1| sprintf("%0.3f",i1).gsub("0.",".")})
        }
      end
    end
  end
end