#
# OrthogonalArray.rb
#
# Copyright (C) 2010 GLAD!! (ITO Yoshiichi)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific language
# governing permissions and limitations under the License.
#

require 'logger'
require 'LinearGraphTemplate'

class OrthogonalArray

  @@log = Logger.new(STDOUT)
  @@log.level = Logger::WARN
  @@log.formatter = proc {|s, d, p, m| sprintf("%-5s %s: %s\n", s, p, m) }

  attr_reader :template, :table

  attr_accessor :row0, :col0, :val0

  def initialize(size, args = '')
    @template = LinearGraphTemplate.new(size, args)
    @table = make_table(@template)
    @row0 = 1
    @col0 = 1
    @val0 = 0
  end

  def make_table(template)
    (0...template.size).map {|i|
       make_row(i, template)
    }
  end

  def make_row(row_idx, template)
    original = make_original_row(row_idx, template.size - 1)
    template.make_row(original)
  end

  def make_original_row(row_idx, n_cols, div = 1)
    if n_cols == 0
      return []
    end
    heads = make_original_row(row_idx, n_cols / 2, div * 2)
    infix = (row_idx / div) % 2
    heads + [infix] + heads.map {|x| x ^ infix }
  end

  def size
    template.size
  end

  def strength
    if !@strength
      @strength = calc_strength
    end
    @strength
  end

  def calc_strength
    cols = template.graphs.size
    for i in 2..cols
      rating = rating(i)
      return i - 1 if rating <  100
      return i     if rating == 100
    end
    cols
  end

  def rating(strength = 2)
    count_of_combi = Hash.new(0)
    combi = (1..template.graphs.size).to_a.combination(strength)
    combi.each {|cols|
      values = make_values(cols)
      counts = make_count_hash(values)
      table.each {|row|
        key = cols.map {|col| row[col - 1] }
        counts[key] += 1
      }
      counts.each {|k, v|
        count_of_combi[v] += 1
      }
    }
    @@log.info { "#{ strength } => #{ count_of_combi.inspect }" }
    numerator = (count_of_combi[0] == 0) ?
        count_of_combi.map {|k, v| k * v }.reduce(:+) :
        count_of_combi.map {|k, v| k > 0 ? v : 0 }.reduce(:+)
    100.0 * numerator / count_of_combi.values.reduce(:+)
  end

  def valid?(strength = 2)
    combi = (1..template.graphs.size).to_a.combination(strength)
    combi.each {|cols|
      values = make_values(cols)
      counts = make_count_hash(values)
      table.each {|row|
        key = cols.map {|col| row[col - 1] }
        counts[key] += 1
      }
      @@log.debug { "#{ cols.inspect } => #{ counts.inspect }" }
      if counts.has_value?(0)
        raise "#{ cols.inspect } => #{ counts.reject {|k, v| v != 0 }.keys.sort }"
      end
    }
    return true
  end

  def make_values(cols)
    if cols.size == 1
      graph = template.graphs[cols[0] - 1]
      count = 2 ** graph.points.size
      return (0...count).map {|x| [x] }
    end
    heads = make_values(cols[0, 1])
    tails = make_values(cols[1..-1])
    values = []
    heads.each {|head|
      tails.each {|tail|
        values << head + tail
      }
    }
    values
  end

  def make_count_hash(keys)
    hash = Hash.new
    keys.each {|key|
      hash[key] = 0
    }
    hash
  end

  def print(out = STDOUT)
    out.puts "\"#{ summary }\""
    out.puts " ,#{ header.join(',') }"
    for i in 0...size
      out.puts "#{ row0 + i },#{ row(i).join(',') }"
    end
  end

  def summary
    #"OA(#{ size }; #{ template.levels.join(', ') }; #{ strength })"
    "OA(#{ size }; #{ template.levels.join(', ') })"
  end

  def header
    (0...template.graphs.count).map {|x| col0 + x }
  end

  def row(idx)
    table[idx].map {|x| val0 + x }
  end

end
