# encoding: utf-8

# Copyright (C) 2016 Mikio Ikoma

require "pp"
require 'regextest/common'

class Regextest::Back::Element
  include Regextest::Common
  def initialize(param)
    # puts "Element param:#{param[:cmd]} data:#{param[:ranges].size}"
    @param = param
    @command = param[:cmd]
    @charset = param[:charset]
    if @command == :CMD_SELECT
      @candidates = param[:ranges].inject([]){|result, range| result += range.to_a}
    end
    # @candidates = param[:data] if @command == :CMD_SELECT
  end
  
  attr_reader :param, :command, :candidates
  
  # random fix
  def random_fix
    if @command == :CMD_SELECT
      offset = (@candidates.size > 1)?TstRand(@candidates.size):0
      result = @candidates[offset]
      @candidates = [result]   # fixed!
    else
      raise "invalid command at random_fix: #{@command}"
    end
    [result].pack("U*")   # tranforms from code point to a letter
  end
  
  # size of candidates
  def size
    if(@candidates)
      @candidates.size
    else
      # raise "internal error: candidates not found at size-method"
      0
    end
  end
  
  # [] of candidates
  def [](num)
    if(@candidates)
      @candidates[num]
    else
      raise "internal error: candidates not found at at-method"
    end
  end
  
  # narrow down candidates
  def intersect(other_obj)
    raise "invalid command at intersect" if(other_obj.command != :CMD_SELECT)
    work = @candidates & other_obj.candidates
    if work.size > 0
      @candidates = work
    else
      nil
    end
  end
  
  # exclude
  def exclude(other_obj)
    raise "invalid command at exclude" if(other_obj.command != :CMD_SELECT)
    work = @candidates - other_obj.candidates
    if work.size > 0
      @candidates = work
    else
      nil
    end
  end
  
  # join candidates
  def union(other_obj)
    raise "invalid command at union" if(other_obj.command != :CMD_SELECT)
    #@candidates |= other_obj.candidates
    @candidates += other_obj.candidates # to be faster
  end
  
  # for simple pretty print
  def inspect
    case @command
    when :CMD_SELECT
      if(@candidates)
        @candidates.inspect
      else
        @param[:ranges].inspect
      end
    when :CMD_LOOK_BEHIND, :CMD_LOOK_AHEAD, :CMD_NOT_LOOK_BEHIND, :CMD_NOT_LOOK_AHEAD
      @param.inspect
    when :CMD_ANC_LINE_BEGIN, :CMD_ANC_LINE_END, :CMD_ANC_WORD_BOUND, :CMD_ANC_WORD_UNBOUND,
         :CMD_ANC_STRING_BEGIN, :CMD_ANC_STRING_END, :CMD_ANC_STRING_END2, :CMD_ANC_MATCH_START,
         :CMD_ANC_LOOK_BEHIND2, :CMD_ANC_RELUCTANT_BEGIN, :CMD_ANC_RELUCTANT_END,
         :CMD_ANC_POSSESSIVE_BEGIN, :CMD_ANC_POSSESSIVE_END
      @param.inspect
    else
      raise "inner error, invalid command #{@command}"
    end
  end
  
  # Includes new line or not
  def new_line?
    @candidates.index(0xa)
  end
  
  # Sets new line
  def set_new_line
    @candidates = [0xa]
  end
  
  # Is word-elements only?
  def word_elements?
    letters = @candidates.map{|elem| [elem].pack("U*")}.join("")
    if @charset == "u" || @charset == "d"
      letters.match(/^\p{Word}+$/)
    else
      letters.match(/^\w+$/)
    end
  end
  
  # is non-word-elements only?
  def non_word_elements?
    letters = @candidates.map{|elem| [elem].pack("U*")}.join("")
    if @charset == "u" || @charset == "d"
      letters.match(/^\p{^Word}+$/)
    else
      letters.match(/^\W+$/)
    end
  end

  # set word-elements
  def set_word_elements
    if @charset == "u" || @charset == "d"
      @candidates.select!{|elem| [elem].pack("U*").match(/^\p{Word}$/)}
    else
      @candidates.select!{|elem| [elem].pack("U*").match(/^\w$/)}
    end
  end
  
  # set non_word-elements
  def set_non_word_elements
    if @charset == "u" || @charset == "d"
      @candidates.select!{|elem| [elem].pack("U*").match(/^\p{^Word}$/)}
      #@candidates.select!{|elem| [elem].pack("U*").match(/^[[:^word:]]$/)}
    else
      @candidates.select!{|elem| [elem].pack("U*").match(/^[[:^word:]]$/)}
    end
  end
  
  # checks empty
  def empty?
    @candidates.size == 0
  end
  
  # factory method to generate any char element
  def self.any_char
    # BUG: must consider other character set!
    Regextest::Back::Element.new({cmd: :CMD_SELECT, ranges:  [0x20..0x7e]})
  end
  
  # factory method to generate any char element
  def reverse
    @candidates = ((0x20..0x7e).to_a) - @candidates
    self
  end
  

end

# Test suite (execute when this file is specified in command line)
if __FILE__ == $0
end


