#--
# Copyright (c) 2009-2010, John Mettraux, jmettraux@gmail.com
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Made in Japan.
#++

require 'rufus/tokyo/utils'
require 'rufus/tokyo/query'
require 'rufus/tokyo/transactions'
require 'rufus/tokyo/openable'

module Rufus::Edo

  #
  # Methods common to the two table classes (table + ntyrant) found in
  # Rufus::Edo
  #
  module TableCore

    include Rufus::Tokyo::HashMethods
    include Rufus::Tokyo::Transactions

    # Add the open() method to all Table type classes.
    def self.included(target)
      target.extend(Rufus::Tokyo::Openable)
    end

    # Closes the table (and frees the datastructure allocated for it),
    # raises an exception in case of failure.
    #
    def close
      @db.close || raise_error
    end

    # Generates a unique id (in the context of this Table instance)
    #
    def generate_unique_id
      @db.genuid
    end
    alias :genuid :generate_unique_id

    INDEX_TYPES = {
      :lexical => 0,
      :decimal => 1,
      :token => 2,
      :qgram => 3,
      :opt => 9998,
      :optimized => 9998,
      :void => 9999,
      :remove => 9999,
      :keep => 1 << 24
    }

    # Sets an index on a column of the table.
    #
    # Types maybe be :lexical or :decimal.
    #
    # Recently (TC 1.4.26 and 1.4.27) inverted indexes have been added,
    # they are :token and :qgram. There is an :opt index as well.
    #
    # Sorry couldn't find any good doc about those inverted indexes apart from :
    #
    #   http://alpha.mixi.co.jp/blog/?p=1147
    #   http://www.excite-webtl.jp/world/english/web/?wb_url=http%3A%2F%2Falpha.mixi.co.jp%2Fblog%2F%3Fp%3D1147&wb_lp=JAEN&wb_dis=2&wb_submit=+%96%7C+%96%F3+
    #
    # Use :keep to "add" and
    # :remove (or :void) to "remove" an index.
    #
    # If column_name is :pk or "", the index will be set on the primary key.
    #
    # Returns true in case of success.
    #
    def set_index (column_name, *types)

      column_name = column_name == :pk ? '' : column_name.to_s

      i = types.inject(0) { |ii, t| ii | INDEX_TYPES[t] }

      @db.setindex(column_name, i) || raise_error
    end

    # Inserts a record in the table db
    #
    #   table['pk0'] = [ 'name', 'fred', 'age', '45' ]
    #   table['pk1'] = { 'name' => 'jeff', 'age' => '46' }
    #
    # Accepts both a hash or an array (expects the array to be of the
    # form [ key, value, key, value, ... ] else it will raise
    # an ArgumentError)
    #
    # Raises an error in case of failure.
    #
    def []= (pk, h_or_a)

      pk = pk.to_s

      m = h_or_a.is_a?(Hash) ? h_or_a : Hash[*h_or_a]
      m = Rufus::Tokyo.h_or_a_to_s(m)

      #verify_value(m)

      @db.put(pk, m) || raise_error
    end

    # Removes an entry in the table
    #
    # (might raise an error if the delete itself failed, but returns nil
    # if there was no entry for the given key)
    #
    # Raises an error if something went wrong
    #
    def delete (k)

      k = k.to_s

      val = @db[k]
      return nil unless val

      @db.out(k) || raise_error
      val
    end

    # Removes all records in this table database
    #
    # Raises an error if something went wrong
    #
    def clear

      @db.vanish || raise_error
    end

    # Returns an array of all the primary keys in the table
    #
    # With no options given, this method will return all the keys (strings)
    # in a Ruby array.
    #
    #   :prefix --> returns only the keys who match a given string prefix
    #
    #   :limit --> returns a limited number of keys
    #
    def keys (options={})

      pref = options.fetch(:prefix, "")

      @db.fwmkeys(pref, options[:limit] || -1)
    end

    # Deletes all the entries whose key begin with the given prefix.
    #
    def delete_keys_with_prefix (prefix)

      query_delete { |q| q.add('', :strbw, prefix) }
    end

    # Returns a hash { key => record } of all the records matching the
    # given keys.
    #
    def lget (*keys)

      keys = Rufus::Tokyo::h_or_a_to_s(keys.flatten)

      if @db.respond_to?(:mget)
        @db.mget(keys)
      else
        keys.inject({}) { |h, k| v = self[k]; h[k] = v if v; h }
      end
    end

    alias :mget :lget

    # Returns the number of records in this table db
    #
    def size

      @db.rnum
    end

    # Prepares a query instance (block is optional)
    #
    def prepare_query (&block)

      q = TableQuery.new(table_query_class, self)
      block.call(q) if block

      q
    end

    # Prepares and runs a query, returns an array of hashes (all Ruby)
    # (takes care of freeing the query and the result set structures)
    #
    def query (&block)

      prepare_query(&block).run
    end

    # Prepares, runs AND delete all the matching records.
    #
    def query_delete (&block)

      prepare_query(&block).delete
    end

    # Prepares, runs AND counts all the matching records.
    #
    def query_count (&block)

      prepare_query { |q|
        q.pk_only  # improve efficiency, since we have to do the query
      }.count
    end

    # Warning : this method is low-level, you probably only need
    # to use #transaction and a block.
    #
    # Direct call for 'transaction begin'.
    #
    def tranbegin

      @db.tranbegin || raise_error
    end

    # Warning : this method is low-level, you probably only need
    # to use #transaction and a block.
    #
    # Direct call for 'transaction commit'.
    #
    def trancommit

      @db.trancommit || raise_error
    end

    # Warning : this method is low-level, you probably only need
    # to use #transaction and a block.
    #
    # Direct call for 'transaction abort'.
    #
    def tranabort

      @db.tranabort || raise_error
    end

    # Returns the underlying 'native' Ruby object (of the class devised by
    # Hirabayashi-san)
    #
    def original

      @db
    end

    # Returns the union of the listed queries
    #
    #   r = table.union(
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'es'
    #     },
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'li'
    #     }
    #   )
    #
    # will return a hash { primary_key => record } of the values matching
    # the first query OR the second.
    #
    # If the last element element passed to this method is the value 'false',
    # the return value will the array of matching primary keys.
    #
    def union (*queries)

      search(:union, *queries)
    end

    # Returns the intersection of the listed queries
    #
    #   r = table.intersection(
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'es'
    #     },
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'li'
    #     }
    #   )
    #
    # will return a hash { primary_key => record } of the values matching
    # the first query AND the second.
    #
    # If the last element element passed to this method is the value 'false',
    # the return value will the array of matching primary keys.
    #
    def intersection (*queries)

      search(:intersection, *queries)
    end

    # Returns the difference of the listed queries
    #
    #   r = table.intersection(
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'es'
    #     },
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'li'
    #     }
    #   )
    #
    # will return a hash { primary_key => record } of the values matching
    # the first query OR the second but not both.
    #
    # If the last element element passed to this method is the value 'false',
    # the return value will the array of matching primary keys.
    #
    def difference (*queries)

      search(:difference, *queries)
    end

    # A #search a la ruby-tokyotyrant
    # (http://github.com/actsasflinn/ruby-tokyotyrant/tree)
    #
    #   r = table.search(
    #     :intersection,
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'es'
    #     },
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'li'
    #     }
    #   )
    #
    # Accepts the symbols :union, :intersection, :difference or :diff as
    # first parameter.
    #
    # If the last element element passed to this method is the value 'false',
    # the return value will the array of matching primary keys.
    #
    def search (type, *queries)

      run_query = true
      run_query = queries.pop if queries.last == false

      raise(
        ArgumentError.new("pass at least one prepared query")
      ) if queries.size < 1

      t = META_TYPES[type]

      raise(
        ArgumentError.new("no search type #{type.inspect}")
      ) unless t

      q = queries.shift.original
      qs = queries.collect { |qq| qq.original }

      pks = q.metasearch(qs, META_TYPES[type])

      run_query ? lget(pks) : pks
    end

    protected

    META_TYPES = {
      :union => 0, :intersection => 1, :difference => 2, :diff => 2
    }

    # Returns the value (as a Ruby Hash) else nil
    #
    # (the actual #[] method is provided by HashMethods)
    #
    def get (k)

      @db.get(k.to_s)
    end

    # Obviously something went wrong, let's ask the db about it and raise
    # an EdoError
    #
    def raise_error

      err_code = @db.ecode
      err_msg = @db.errmsg(err_code)

      raise EdoError.new("(err #{err_code}) #{err_msg}")
    end

    #def verify_value (h)
    #  h.each { |k, v|
    #    next if k.is_a?(String) and v.is_a?(String)
    #    raise ArgumentError.new(
    #      "only String keys and values are accepted " +
    #      "( #{k.inspect} => #{v.inspect} )")
    #  }
    #end
  end

  #
  # A query on a Tokyo Cabinet table db
  #
  class TableQuery

    include Rufus::Tokyo::QueryConstants

    # Creates a query for a given Rufus::Tokyo::Table
    #
    # Queries are usually created via the #query (#prepare_query #do_query)
    # of the Table instance.
    #
    # Methods of interest here are :
    #
    #   * #add (or #add_condition)
    #   * #order_by
    #   * #limit
    #
    # also
    #
    #   * #pk_only
    #   * #no_pk
    #
    def initialize (query_class, table)

      @table   = table
      @query   = query_class.new(table.original)
      @opts    = {}
      @has_run = false
    end

    # Returns the original, underlying RDBQUERY instance.
    #
    def original

      @query
    end

    # Adds a condition
    #
    #   table.query { |q|
    #     q.add 'name', :equals, 'Oppenheimer'
    #     q.add 'age', :numgt, 35
    #   }
    #
    # Understood 'operators' :
    #
    #   :streq # string equality
    #   :eq
    #   :eql
    #   :equals
    #
    #   :strinc # string include
    #   :inc # string include
    #   :includes # string include
    #
    #   :strbw # string begins with
    #   :bw
    #   :starts_with
    #   :strew # string ends with
    #   :ew
    #   :ends_with
    #
    #   :strand # string which include all the tokens in the given exp
    #   :and
    #
    #   :stror # string which include at least one of the tokens
    #   :or
    #
    #   :stroreq # string which is equal to at least one token
    #
    #   :strorrx # string which matches the given regex
    #   :regex
    #   :matches
    #
    #   # numbers...
    #
    #   :numeq # equal
    #   :numequals
    #   :numgt # greater than
    #   :gt
    #   :numge # greater or equal
    #   :ge
    #   :gte
    #   :numlt # greater or equal
    #   :lt
    #   :numle # greater or equal
    #   :le
    #   :lte
    #   :numbt # a number between two tokens in the given exp
    #   :bt
    #   :between
    #
    #   :numoreq # number which is equal to at least one token
    #
    #   :ftsph # full-text phrase search
    #   :ftsphrase
    #   :phrase
    #   :ftsand # full-text AND
    #   :ftsor # full-text OR
    #   :ftsex # full-text with 'compound' expression
    #
    def add (colname, operator, val, affirmative=true, no_index=false)

      colname = colname.to_s
      val = val.to_s

      op = operator.is_a?(Fixnum) ? operator : OPERATORS[operator]
      op = op | TDBQCNEGATE unless affirmative
      op = op | TDBQCNOIDX if no_index

      @query.addcond(colname, op, val)
    end
    alias :add_condition :add

    # Sets the max number of records to return for this query.
    #
    # (If you're using TC >= 1.4.10 the optional 'offset' (skip) parameter
    # is accepted)
    #
    def limit (i, offset=-1)

      @query.setlimit(i, offset)
    end

    # Sets the sort order for the result of the query
    #
    # The 'direction' may be :
    #
    #   :strasc # string ascending
    #   :strdesc
    #   :asc # string ascending
    #   :desc
    #   :numasc # number ascending
    #   :numdesc
    #
    def order_by (colname, direction=:strasc)

      @query.setorder(colname.to_s, DIRECTIONS[direction])
    end

    # When set to true, only the primary keys of the matching records will
    # be returned.
    #
    def pk_only (on=true)

      @opts[:pk_only] = on
    end

    # When set to true, the :pk (primary key) is not inserted in the record
    # (hashes) returned
    #
    def no_pk (on=true)

      @opts[:no_pk] = on
    end

    # Process each record using the supplied block, which will be passed
    # two parameters, the primary key and the value hash.
    #
    # The block passed to this method accepts two parameters : the [String]
    # primary key and a Hash of the values for the record.
    #
    # The return value of the passed block does matter. Three different
    # values are expected :stop, :delete or a Hash instance.
    #
    # :stop will make the iteration stop, further matching records will not
    # be passed to the block
    #
    # :delete will let Tokyo Cabinet delete the record just seen.
    #
    # a Hash is passed to let TC update the values for the record just seen.
    #
    # Passing an array is possible : [ :stop, { 'name' => 'Toto' } ] will
    # update the record just seen to a unique column 'name' and will stop the
    # iteration. Likewise, returning [ :stop, :delete ] will work as well.
    #
    def process (&block)

      @query.proc() do |key, val|

        r = block.call(key, val)
        r = [ r ] unless r.is_a?(Array)

        if updated_value = r.find { |e| e.is_a?(Hash) }
          val.merge!(updated_value)
        end

        r.inject(0) { |i, v|
          case v
          when :stop then i = i | 1 << 24
          when :delete then i = i | 2
          when Hash then i = i | 1
          end
          i
        }
      end

      self
    end

    # Runs this query (returns a TableResultSet instance)
    #
    def run
      @has_run        = true
      @last_resultset = TableResultSet.new(@table, @query.search, @opts)
    end

    # Runs this query AND immediately let the matching records get deleted.
    #
    def delete

      @query.searchout || @table.raise_error
    end

    # Returns the count of results this query return when last run.
    # Returns 0 if the query was not yet run.
    #
    def count

      #@query.count
        # not yet implemented by Hirabayashi-san
      run.free unless @has_run
      @last_resultset ? @last_resultset.size : 0
    end

    # Frees this data structure
    #
    def free

      # nothing ... :(  I hope there's no memory leak
    end

    alias :close :free
    alias :destroy :free
  end

  #
  # The thing queries return
  #
  class TableResultSet
    include Enumerable

    def initialize (table, primary_keys, query_opts)

      @table = table
      @keys = primary_keys
      @opts = query_opts
    end

    # Returns the count of element in this result set
    #
    def size

      @keys.size
    end

    alias :length :size

    # The classical each
    #
    def each

      @keys.each do |pk|
        if @opts[:pk_only]
          yield(pk)
        else
          val = @table[pk]
          val[:pk] = pk unless @opts[:no_pk]
          yield(val)
        end
      end
    end

    # Returns an array of hashes
    #
    def to_a

      self.collect { |m| m }
    end

    # Frees this query (the underlying Tokyo Cabinet list structure)
    #
    def free

      # nothing to do, kept for similarity with Rufus::Tokyo
    end

    alias :close :free
    alias :destroy :free
  end
end


#--
# Copyright (c) 2009-2010, John Mettraux, jmettraux@gmail.com
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Made in Japan.
#++

require 'rufus/tokyo/hmethods'
require 'rufus/tokyo/transactions'
require 'rufus/tokyo/openable'

module Rufus::Edo

  #
  # The base cabinet methods are gathered here. They focus on wrapping
  # Hirabayashi-san's methods.
  #
  # This module is included by Edo::Cabinet and Edo::NTyrant. Placing the
  # methods in this module avoids having to load 'TokyoCabinet' when using
  # only rufus/edo/ntyrant.
  #
  module CabinetCore

    include Rufus::Tokyo::HashMethods
    include Rufus::Tokyo::Transactions

    # Add the open() method to all Cabinet type classes.
    def self.included(target)
      target.extend(Rufus::Tokyo::Openable)
    end

    # Returns the path to this database.
    #
    def path

      @path
    end

    # No comment
    #
    def []= (k, v)

      k = k.to_s; v = v.to_s

      @db.put(k, v) || raise_error
    end

    # Like #put but doesn't overwrite the value if already set. Returns true
    # only if there no previous entry for k.
    #
    def putkeep (k, v)

      k = k.to_s; v = v.to_s

      @db.putkeep(k, v)
    end

    # Appends the given string at the end of the current string value for key k.
    # If there is no record for key k, a new record will be created.
    #
    # Returns true if successful.
    #
    def putcat (k, v)

      k = k.to_s; v = v.to_s

      @db.putcat(k, v)
    end

    # (The actual #[] method is provided by HashMethods)
    #
    def get (k)

      @db.get(k.to_s)
    end
    protected :get

    # Removes a record from the cabinet, returns the value if successful
    # else nil.
    #
    def delete (k)

      k = k.to_s
      v = self[k]

      @db.out(k) ? v : nil
    end

    # Returns the number of records in the 'cabinet'
    #
    def size

      @db.rnum
    end

    # Removes all the records in the cabinet (use with care)
    #
    # Returns self (like Ruby's Hash does).
    #
    def clear

      @db.vanish || raise_error

      self
    end

    # Returns the 'weight' of the db (in bytes)
    #
    def weight

      @db.fsiz
    end

    # Closes the cabinet (and frees the datastructure allocated for it),
    # returns true in case of success.
    #
    def close

      @db.close || raise_error
    end

    # Copies the current cabinet to a new file.
    #
    # Returns true if it was successful.
    #
    def copy (target_path)

      @db.copy(target_path)
    end

    # Copies the current cabinet to a new file.
    #
    # Does it by copying each entry afresh to the target file. Spares some
    # space, hence the 'compact' label...
    #
    def compact_copy (target_path)

      @other_db = self.class.new(target_path)
      self.each { |k, v| @other_db[k] = v }
      @other_db.close
    end

    # "synchronize updated contents of an abstract database object with
    # the file and the device"
    #
    def sync

      @db.sync || raise_error
    end

    # Returns an array of all the primary keys in the db.
    #
    # With no options given, this method will return all the keys (strings)
    # in a Ruby array.
    #
    #   :prefix --> returns only the keys who match a given string prefix
    #
    #   :limit --> returns a limited number of keys
    #
    def keys (options={})

      if @db.respond_to? :fwmkeys
        pref = options.fetch(:prefix, "")

        @db.fwmkeys(pref, options[:limit] || -1)
      elsif @db.respond_to? :range
        @db.range("[min,max]", nil)
      else
        raise NotImplementedError, "Database does not support keys()"
      end
    end

    # Deletes all the entries whose keys begin with the given prefix
    #
    def delete_keys_with_prefix (prefix)

      # only ADB has the #misc method...

      if @db.respond_to?(:misc)
        @db.misc('outlist', @db.fwmkeys(prefix, -1))
      else
        @db.fwmkeys(prefix, -1).each { |k| self.delete(k) }
      end

      nil
    end

    # Given a list of keys, returns a Hash { key => value } of the
    # matching entries (in one sweep).
    #
    # Warning : this is a naive (slow) implementation.
    #
    def lget (*keys)

      keys = keys.flatten.collect { |k| k.to_s }

      # only ADB has the #misc method...

      if @db.respond_to?(:misc)
        Hash[*@db.misc('getlist', keys)]
      else
        keys.inject({}) { |h, k| v = self[k]; h[k] = v if v; h }
      end
    end

    alias :mget :lget

    # Default impl provided by HashMethods
    #
    def merge! (hash)

      # only ADB has the #misc method...

      if @db.respond_to?(:misc)
        @db.misc('putlist', hash.to_a.flatten)
      else
        super(hash)
      end
      self
    end
    alias :lput :merge!

    # Given a list of keys, deletes all the matching entries (in one sweep).
    #
    # Warning : this is a naive (slow) implementation.
    #
    def ldelete (*keys)

      keys = keys.flatten.collect { |k| k.to_s }

      # only ADB has the #misc method...

      if @db.respond_to?(:misc)
        @db.misc('outlist', keys)
      else
        keys.each { |k| self.delete(k) }
      end

      nil
    end

    # Increments the value stored under the given key with the given increment
    # (defaults to 1 (integer)).
    #
    # Warning : Tokyo Cabinet/Tyrant doesn't store counter values as regular
    # strings (db['key'] won't yield something that replies properly to #to_i)
    #
    # Use #counter_value(k) to get the current value set for the counter.
    #
    def incr (key, val=1)

      key = key.to_s

      v = val.is_a?(Fixnum) ? @db.addint(key, val) : @db.adddouble(key, val)

      raise(EdoError.new(
        "incr failed, there is probably already a string value set " +
        "for the key '#{key}'. Make sure there is no value before incrementing"
      )) unless v

      v
    end
    alias :adddouble :incr
    alias :addint :incr
    alias :add_double :incr
    alias :add_int :incr

    # Returns the current value for a counter (a float or an int).
    #
    # See #incr
    #
    def counter_value (key)

      incr(key, 0.0) rescue incr(key, 0)
    end

    # Triggers a defrag (TC >= 1.4.21 only)
    #
    def defrag

      #raise(NotImplementedError.new(
      #  "defrag (misc) only available when opening db with :type => :abstract"
      #)) unless @db.respond_to?(:misc)

      @db.misc('defrag', [])
    end

    # Returns the underlying 'native' Ruby object (of the class devised by
    # Hirabayashi-san)
    #
    def original

      @db
    end

    # This is rather low-level, you'd better use #transaction like in
    #
    #   db.transaction do
    #     db['a'] = 'alpha'
    #     db['b'] = 'bravo'
    #     db.abort if something_went_wrong?
    #   end
    #
    # Note that fixed-length dbs do not support transactions. It will result
    # in a NoMethodError.
    #
    def tranbegin
      @db.tranbegin
    end

    # This is rather low-level use #transaction and a block for a higher-level
    # technique.
    #
    # Note that fixed-length dbs do not support transactions. It will result
    # in a NoMethodError.
    #
    def trancommit
      @db.trancommit
    end

    # This is rather low-level use #transaction and a block for a higher-level
    # technique.
    #
    # Note that fixed-length dbs do not support transactions. It will result
    # in a NoMethodError.
    #
    def tranabort
      @db.tranabort
    end

    protected

    def raise_error
      code = @db.ecode
      message = @db.errmsg(code)
      raise EdoError.new("(err #{code}) #{message}")
    end
  end
end


module DynamicNestedForms
  module ViewHelpers
    # .nested-container
    #   .nested-autocomplete
    #   .nested-items
    #    .nested-item
    #     .nested-content
    #     .nested-value
    #     .remove-item
    def autocomplete_to_add_item(name, f, association, source, options = {})
      new_object              = f.object.send(association).klass.new
      options[:class]         = ["autocomplete add-item", options[:class]].compact.join " "
      options[:data]          ||= {}
      options[:data][:id]     = new_object.object_id
      options[:data][:source] = source
      options[:data][:item]   = f.fields_for(association, new_object, child_index: options[:data][:id]) do |builder|
        render(association.to_s.singularize + "_item", f: builder).gsub "\n", ""
      end

      text_field_tag "autocomplete_nested_content", nil, options
    end

    # def link_to_add_item(name, f, association, options = {})
    #   options = data_attr name, f, association, options
    #   link_to name, "#", options
    # end
    #
    # def data_attr(name, f, association, options = {})
    #   new_object       = f.object.send(association).klass.new
    #   options[:class]  = ["add-item", options[:class]].compact.join " "
    #   options[:id]     = new_object.object_id
    #   options[:item] = f.fields_for(association, new_object, child_index: options[:id]) do |builder|
    #     render(association.to_s.singularize + "_item", f: builder).gsub "\n", ""
    #   end
    #   options
    # end

    def link_to_remove_item(name = nil, options = {})
      name ||= "Remove"
      options[:class] = ["remove-item", options[:class]].compact.join " "
      link_to name, "#", options
    end
  end
end


#--
# Copyright (c) 2009-2010, Jeremy Hinegardner, jeremy@copiousfreetime.org
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Developed with and sponsored by Seven Scale <http://sevenscale.com/>,
# creators of Open Syslog.
#
#++
require 'rufus/tokyo/openable'

module Rufus::Tokyo::Dystopia
  #
  # Tokyo Dystopia Indexed Database
  #
  # http://tokyocabinet.sourceforge.net/dystopiadoc/#dystopiaapi
  #
  class Core
    extend Rufus::Tokyo::Openable

    class Error < Rufus::Tokyo::Dystopia::Error; end

    def self.lib
      ::Rufus::Tokyo::DystopiaLib
    end

    def self.mode_to_bits( mode )
      @modes_to_bits ||= {
        "r"  => lib::READER,
        "r+" => lib::READER | lib::WRITER,
        "w"  => lib::WRITER | lib::CREAT | lib::TRUNC,
        "w+" => lib::READER | lib::WRITER | lib::CREAT | lib::TRUNC,
        "a"  => lib::WRITER | lib::CREAT,
        "a+" => lib::READER | lib::WRITER | lib::CREAT ,
      }

      return @modes_to_bits[mode]
    end

    def self.locking_to_bits( locking )
      @locking_to_bits ||= {
        true  => 0,
        false => lib::NOLCK,
        :nonblocking => lib::LCKNB
      }
      return @locking_to_bits[locking]
    end

    def lib
      Core.lib
    end

    #
    # Opens/creates a new Tokyo dystopia database
    #
    # The modes are equivelent to those when opening a file:
    #
    # 'r'  : readonly
    # 'r+' : read/write does not create or truncate
    # 'w'  : write only, create and truncate
    # 'w+' : read/write, create and truncate
    # 'a'  : write only, create if db does not exist
    # 'a+' : read/write, create if db does not exist
    #
    # The third parameter 'locking' can be one of 'true', 'false' or
    # :nonblocking
    #
    def initialize( path, mode = "a+", locking = true )
      mode_bits = Core.mode_to_bits( mode )
      raise Error.new( "Invalid mode '#{mode}'" ) unless mode_bits
      lock_bits = Core.locking_to_bits( locking )
      raise Error.new( "Invalid Locking mode #{locking}" ) unless lock_bits

      @db = lib.tcidbnew()

      lib.tcidbopen( @db, path, mode_bits | lock_bits ) || raise_error
    end

    #
    # Close and detach from the database.  This instance can not be used anymore
    #
    def close
      lib.tcidbclose( @db ) || raise_error

      lib.tcidbdel( @db )

      @db = nil
    end

    #
    # Add a new document to the database
    #
    def store( id, text )
      lib.tcidbput( @db, id, text ) || raise_error
    end

    #
    # Remove the given document from the index
    #
    def delete( id )
      lib.tcidbout( @db, id ) || raise_error
    end

    #
    # Return the document at the specified index
    #
    def fetch( id )
      r = nil
      begin
        r = lib.tcidbget( @db, id )
      rescue => e
        # if we have 'no record found' then return nil
        if lib.tcidbecode( @db ) == 22 then
          return nil
        else
          raise_error
        end
      end
      return r
    end

    #
    # Return the document ids of the documents that matche the search expression
    #
    # http://tokyocabinet.sourceforge.net/dystopiadoc/#dystopiaapi and scroll
    # down to 'Compound Expression of Search'
    #
    def search( expression )
      out_count = ::FFI::MemoryPointer.new :pointer
      out_list  = ::FFI::MemoryPointer.new :pointer
      out_list  = lib.tcidbsearch2( @db, expression, out_count )

      count = out_count.read_int
      results = out_list.get_array_of_uint64(0, count )
      return results
    end

    #
    # Remove all records from the db
    #
    def clear
      lib.tcidbvanish( @db )
    end

    #
    # Report the path of the database
    #
    def path
      s = lib.tcidbpath( @db )
      return File.expand_path( s ) if s
    end

    #
    # Return the number of records in the database
    #
    def count
      lib.tcidbrnum( @db )
    end
    alias :rnum :count

    #
    # return the disk space used by the index
    #
    def fsize
      lib.tcidbfsiz( @db )
    end

    protected

    #
    # Raises a dystopian error (asks the db which one)
    #
    def raise_error
      code = lib.tcidbecode( @db )
      msg  = lib.tcidberrmsg( code )
      raise Error.new("[ERROR #{code}] : #{msg}")
    end

  end
end


# Symbolizes all of hash's keys and subkeys.
# Also allows for custom pre-processing of keys (e.g. downcasing, etc)
# if the block is given:
#
# somehash.deep_symbolize { |key| key.downcase }
#
# Usage: either include it into global Hash class to make it available to
#        to all hashes, or extend only your own hash objects with this
#        module.
#        E.g.:
#        1) class Hash; include DeepSymbolizable; end
#        2) myhash.extend DeepSymbolizable

module DeepSymbolizable
  def deep_symbolize(&block)
    method = self.class.to_s.downcase.to_sym
    syms = DeepSymbolizable::Symbolizers
    syms.respond_to?(method) ? syms.send(method, self, &block) : self
  end

  module Symbolizers
    extend self

    # the primary method - symbolizes keys of the given hash,
    # preprocessing them with a block if one was given, and recursively
    # going into all nested enumerables
    def hash(hash, &block)
      hash.inject({}) do |result, (key, value)|
        # Recursively deep-symbolize subhashes
        value = _recurse_(value, &block)

        # Pre-process the key with a block if it was given
        key = yield key if block_given?
        # Symbolize the key string if it responds to to_sym
        sym_key = key.to_sym rescue key

        # write it back into the result and return the updated hash
        result[sym_key] = value
        result
      end
    end

    # walking over arrays and symbolizing all nested elements
    def array(ary, &block)
      ary.map { |v| _recurse_(v, &block) }
    end

    # handling recursion - any Enumerable elements (except String)
    # is being extended with the module, and then symbolized
    def _recurse_(value, &block)
      if value.is_a?(Enumerable) && !value.is_a?(String)
        # support for a use case without extended core Hash
        value.extend DeepSymbolizable unless value.class.include?(DeepSymbolizable)
        value = value.deep_symbolize(&block)
      end
      value
    end
  end

end


require 'game_icons/db'

module GameIcons
  class DidYouMean
    def initialize(names)
      @letter_freqs = lex(names)
    end

    def query(q, result_size = 5)
      q = scrub(q.downcase.strip)
      q_freq = char_freq(q)
      scores = Hash.new(0)
      @letter_freqs.each do |name, freq|
        scores[name] = score(freq, q_freq)
      end
      top(result_size, scores)
    end

    private
    def lex(names)
      names.inject({}) do |freqs, name|
        freqs[name] = char_freq(scrub(name.downcase.strip))
        freqs
      end
    end

    # Computes a hash of each character
    def char_freq(str)
      freqs = Hash.new(0)
      (1..4).each do |i|
        str.chars.each_cons(i).inject(freqs) do |freq, ngram|
          ngram = ngram.join
          freq[ngram] = freq[ngram] + 1
          freq
        end
      end
      freqs
    end

    def scrub(str)
      str.gsub(/[^a-z]/i, '')
    end

    def score(n_freq, q_freq)
      score = 0
      # Go over every freq in the query

      # q_freq.each do |c, count|
      #   score += (n_freq[c] - count).abs
      # end
      # Go over everything in n that was not in query
      (n_freq.keys + q_freq.keys).uniq.each do |k|
        score += (n_freq[k] - q_freq[k]).abs
      end
      score
    end

    # Return top scoring, sorted by lowest
    def top(n, scores)
      scores.sort {|a,b| a[1] <=> b[1]}.map{|x| x[0]}.first(n)
    end

  end
end


require 'game_icons/optional_deps'

module GameIcons
  class Icon
    attr_reader :file

    def initialize(file)
      @file = file
    end

    def string
      @svgstr ||= File.open(@file) { |f| f.read }
    end

    # Modify the background and foreground colors and their opacities
    def recolor(bg: '#000', fg: '#fff', bg_opacity: "1.0", fg_opacity: "1.0")
      OptionalDeps.require_nokogiri
      bg.prepend('#') unless bg.start_with? '#'
      fg.prepend('#') unless fg.start_with? '#'
      doc     = Nokogiri::XML(self.string)
      doc.css('path')[0]['fill'] = bg # dark backdrop
      doc.css('path')[1]['fill'] = fg # light drawing
      doc.css('path')[0]['fill-opacity'] = bg_opacity.to_s # dark backdrop
      doc.css('path')[1]['fill-opacity'] = fg_opacity.to_s # light drawing
      @svgstr = doc.to_xml
      self
    end

    # Fix an incompatibility issue with Gimp & Inkscape
    # Replaces path strings like "1.5-1.5" with "1.5 -1.5"
    def correct_pathdata
      10.times do # this is a bit of a hack b/c my regex isn't perfect
        @svgstr = self.string
          .gsub(/(\d)\-/,'\1 -')                  # separate negatives
          .gsub(/(\.)(\d+)(\.)/,'\1\2 \3') # separate multi-decimals
        end
      self
    end

  end
end


#--
# Copyright (c) 2009-2010, Adam Keys, John Mettraux, jmettraux@gmail.com
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Made in Japan.
#++

module Rufus::Tokyo
  module Outlen

    # A wrapper for library returning a string (binary data potentially)
    #
    def outlen_op (method, *args)

      args.unshift(@db)

      outlen = FFI::MemoryPointer.new(:int)
      args << outlen

      out = lib.send(method, *args)

      return nil if out.address == 0

      out.get_bytes(0, outlen.get_int(0))

    ensure
      outlen.free
      lib.tcfree(out)
      #lib.free(out)
    end
  end
end


#--
# Copyright (c) 2009-2010, John Mettraux, jmettraux@gmail.com
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Made in Japan.
#++

require 'rufus/tokyo/transactions'
require 'rufus/tokyo/outlen'
#require 'rufus/tokyo/config'
require 'rufus/tokyo/openable'

module Rufus::Tokyo

  #
  # A 'cabinet', ie a Tokyo Cabinet [abstract] database.
  #
  # Follows the abstract API described at :
  #
  #   http://tokyocabinet.sourceforge.net/spex-en.html#tcadbapi
  #
  # An usage example :
  #
  #   db = Rufus::Tokyo::Cabinet.new('test_data.tch')
  #   db['pillow'] = 'Shonagon'
  #
  #   db.size # => 1
  #   db['pillow'] # => 'Shonagon'
  #
  #   db.delete('pillow') # => 'Shonagon'
  #   db.size # => 0
  #
  #   db.close
  #
  class Cabinet

    include HashMethods
    include Transactions
    include Outlen
    #include CabinetConfig
    extend Openable

    # Creates/opens the cabinet, raises an exception in case of
    # creation/opening failure.
    #
    # This method accepts a 'name' parameter and an optional 'params' hash
    # parameter.
    #
    # 'name' follows the syntax described at
    #
    #   http://tokyocabinet.sourceforge.net/spex-en.html#tcadbapi
    #
    # under tcadbopen(). For example :
    #
    #   db = Rufus::Tokyo::Cabinet.new('casket.tch#bnum=100000#opts=ld')
    #
    # will open (eventually create) a hash database backed in the file
    # 'casket.tch' with a bucket number of 100000 and the 'large' and
    # 'deflate' options (opts) turned on.
    #
    # Note that there is an #open method similar to File#open for openening
    # a db and closing it when it's no longer needed :
    #
    #   Rufus::Tokyo::Cabinet.new('data.tch') do |db|
    #     db['key'] = value
    #   end
    #
    # == database name
    #
    # From http://tokyocabinet.sourceforge.net/spex-en.html#tcadbapi :
    #
    # 'If it is "*", the database will be an on-memory hash database. If it is
    #  "+", the database will be an on-memory tree database. If its suffix is
    #  ".tch", the database will be a hash database. If its suffix is ".tcb",
    #  the database will be a B+ tree database. If its suffix is ".tcf", the
    #  database will be a fixed-length database. If its suffix is ".tct", the
    #  database will be a table database.'
    #
    # You're supposed to give a path to the database file you want to use and
    # Cabinet expects you to give the proper prefix.
    #
    #   db = Rufus::Tokyo::Cabinet.new('data.tch') # hash database
    #   db = Rufus::Tokyo::Cabinet.new('data.tcb') # B+ tree db
    #   db = Rufus::Tokyo::Cabinet.new('data.tcf') # fixed-length db
    #
    # will result with the same file names :
    #
    #   db = Rufus::Tokyo::Cabinet.new('data', :type => :hash) # hash database
    #   db = Rufus::Tokyo::Cabinet.new('data', :type => :btree) # B+ tree db
    #   db = Rufus::Tokyo::Cabinet.new('data', :type => :fixed) # fixed-length db
    #
    # You can open an in-memory hash and an in-memory B+ tree with :
    #
    #   h = Rufus::Tokyo::Cabinet.new(:mem_hash) # or
    #   h = Rufus::Tokyo::Cabinet.new('*')
    #
    #   t = Rufus::Tokyo::Cabinet.new(:mem_tree) # or
    #   t = Rufus::Tokyo::Cabinet.new('+')
    #
    # == parameters
    #
    # There are two ways to pass parameters at the opening of a db :
    #
    #   db = Rufus::Tokyo::Cabinet.new('data.tch#opts=ld#mode=w') # or
    #   db = Rufus::Tokyo::Cabinet.new('data.tch', :opts => 'ld', :mode => 'w')
    #
    # most verbose :
    #
    #   db = Rufus::Tokyo::Cabinet.new(
    #     'data', :type => :hash, :opts => 'ld', :mode => 'w')
    #
    # === :mode
    #
    #   * :mode    a set of chars ('r'ead, 'w'rite, 'c'reate, 't'runcate,
    #              'e' non locking, 'f' non blocking lock), default is 'wc'
    #
    # === :default and :default_proc
    #
    # Much like a Ruby Hash, a Cabinet accepts a default value or a default_proc
    #
    #   db = Rufus::Tokyo::Cabinet.new('data.tch', :default => 'xxx')
    #   db['fred'] = 'Astaire'
    #   p db['fred'] # => 'Astaire'
    #   p db['ginger'] # => 'xxx'
    #
    #   db = Rufus::Tokyo::Cabinet.new(
    #     'data.tch',
    #     :default_proc => lambda { |cab, key| "not found : '#{k}'" }
    #   p db['ginger'] # => "not found : 'ginger'"
    #
    # The first arg passed to the default_proc is the cabinet itself, so this
    # opens up interesting possibilities.
    #
    #
    # === other parameters
    #
    # 'On-memory hash database supports "bnum", "capnum", and "capsiz".
    #  On-memory tree database supports "capnum" and "capsiz".
    #  Hash database supports "mode", "bnum", "apow", "fpow", "opts",
    #  "rcnum", and "xmsiz".
    #  B+ tree database supports "mode", "lmemb", "nmemb", "bnum", "apow",
    #  "fpow", "opts", "lcnum", "ncnum", and "xmsiz".
    #  Fixed-length database supports "mode", "width", and "limsiz"'
    #
    #   * :opts    a set of chars ('l'arge, 'd'eflate, 'b'zip2, 't'cbs)
    #              (usually empty or something like 'ld' or 'lb')
    #
    #   * :bnum    number of elements of the bucket array
    #   * :apow    size of record alignment by power of 2 (defaults to 4)
    #   * :fpow    maximum number of elements of the free block pool by
    #              power of 2 (defaults to 10)
    #   * :mutex   when set to true, makes sure only 1 thread at a time
    #              accesses the table (well, Ruby, global thread lock, ...)
    #
    #   * :rcnum   specifies the maximum number of records to be cached.
    #              If it is not more than 0, the record cache is disabled.
    #              It is disabled by default.
    #   * :lcnum   specifies the maximum number of leaf nodes to be cached.
    #              If it is not more than 0, the default value is specified.
    #              The default value is 2048.
    #   * :ncnum   specifies the maximum number of non-leaf nodes to be
    #              cached. If it is not more than 0, the default value is
    #              specified. The default value is 512.
    #
    #   * :xmsiz   specifies the size of the extra mapped memory. If it is
    #              not more than 0, the extra mapped memory is disabled.
    #              The default size is 67108864.
    #
    #   * :capnum  specifies the capacity number of records.
    #   * :capsiz  specifies the capacity size of using memory.
    #
    #   * :dfunit  unit step number. If it is not more than 0,
    #              the auto defragmentation is disabled. (Since TC 1.4.21)
    #
    #
    # = NOTE :
    #
    # On reopening a file, Cabinet will tend to stick to the parameters as
    # set when the file was opened. To change that, have a look at the
    # man pages of the various command line tools coming with Tokyo Cabinet.
    #
    def initialize (name, params={})

      #conf = determine_conf(path, params)
        # not using it

      @db = lib.tcadbnew

      name = '*' if name == :mem_hash # in memory hash database
      name = '+' if name == :mem_tree # in memory B+ tree database

      if type = params.delete(:type)
        name += { :hash => '.tch', :btree => '.tcb', :fixed => '.tcf' }[type]
      end

      @path = name
      @type = File.extname(@path)[1..-1]

      name = name + params.collect { |k, v| "##{k}=#{v}" }.join('')

      lib.tcadbopen(@db, name) || raise(
        TokyoError.new("failed to open/create db '#{name}' #{params.inspect}"))

      #
      # default value|proc

      self.default = params[:default]
      @default_proc ||= params[:default_proc]
    end

    # Returns a new in-memory hash. Accepts the same optional params hash
    # as new().
    #
    def self.new_hash (params={})

      self.new(:hash, params)
    end

    # Returns a new in-memory B+ tree. Accepts the same optional params hash
    # as new().
    #
    def self.new_tree (params={})

      self.new(:tree, params)
    end

    # Using the cabinet lib
    #
    def lib

      CabinetLib
    end

    # Returns the path to this database.
    #
    def path

      @path
    end

    # No comment
    #
    def []= (k, v)

      k = k.to_s; v = v.to_s

      lib.abs_put(@db, k, Rufus::Tokyo.blen(k), v, Rufus::Tokyo.blen(v))
    end

    # Like #put but doesn't overwrite the value if already set. Returns true
    # only if there no previous entry for k.
    #
    def putkeep (k, v)

      k = k.to_s; v = v.to_s

      lib.abs_putkeep(@db, k, Rufus::Tokyo.blen(k), v, Rufus::Tokyo.blen(v))
    end

    # Appends the given string at the end of the current string value for key k.
    # If there is no record for key k, a new record will be created.
    #
    # Returns true if successful.
    #
    def putcat (k, v)

      k = k.to_s; v = v.to_s

      lib.abs_putcat(@db, k, Rufus::Tokyo.blen(k), v, Rufus::Tokyo.blen(v))
    end

    # (The actual #[] method is provided by HashMethods
    #
    def get (k)

      k = k.to_s

      outlen_op(:abs_get, k, Rufus::Tokyo.blen(k))
    end
    protected :get

    # Removes a record from the cabinet, returns the value if successful
    # else nil.
    #
    def delete (k)

      k = k.to_s

      v = self[k]

      lib.abs_out(@db, k, Rufus::Tokyo.blen(k)) ? v : nil
    end

    # Returns the number of records in the 'cabinet'
    #
    def size

      lib.abs_rnum(@db)
    end

    # Removes all the records in the cabinet (use with care)
    #
    # Returns self (like Ruby's Hash does).
    #
    def clear

      lib.abs_vanish(@db)

      self
    end

    # Returns the 'weight' of the db (in bytes)
    #
    def weight

      lib.abs_size(@db)
    end

    # Closes the cabinet (and frees the datastructure allocated for it),
    # returns true in case of success.
    #
    def close

      result = lib.abs_close(@db)
      lib.abs_del(@db)

      result
    end

    # Copies the current cabinet to a new file.
    #
    # Returns true if it was successful.
    #
    def copy (target_path)

      lib.abs_copy(@db, target_path)
    end

    # Copies the current cabinet to a new file.
    #
    # Does it by copying each entry afresh to the target file. Spares some
    # space, hence the 'compact' label...
    #
    def compact_copy (target_path)

      @other_db = Cabinet.new(target_path)
      self.each { |k, v| @other_db[k] = v }
      @other_db.close
    end

    # "synchronize updated contents of an abstract database object with
    # the file and the device"
    #
    def sync

      lib.abs_sync(@db)
    end

    # Returns an array with all the keys in the databse
    #
    # With no options given, this method will return all the keys (strings)
    # in a Ruby array.
    #
    #   :prefix --> returns only the keys who match a given string prefix
    #
    #   :limit --> returns a limited number of keys
    #
    #   :native --> returns an instance of Rufus::Tokyo::List instead of
    #     a Ruby Hash, you have to call #free on that List when done with it !
    #     Else you're exposing yourself to a memory leak.
    #
    def keys (options={})

      if @type == "tcf"
        min, max = "min", "max"
        l        = lib.tcfdbrange2( as_fixed, min, Rufus::Tokyo.blen(min),
                                              max, Rufus::Tokyo.blen(max), -1)
      else
        pre = options.fetch(:prefix, "")

        l = lib.abs_fwmkeys(
          @db, pre, Rufus::Tokyo.blen(pre), options[:limit] || -1)
      end

      l = Rufus::Tokyo::List.new(l)

      options[:native] ? l : l.release
    end

    # Deletes all the entries whose keys begin with the given prefix
    #
    def delete_keys_with_prefix (prefix)

      call_misc(
        'outlist', lib.abs_fwmkeys(@db, prefix, Rufus::Tokyo.blen(prefix), -1))
          # -1 for no limits

      nil
    end

    # Given a list of keys, returns a Hash { key => value } of the
    # matching entries (in one sweep).
    #
    def lget (*keys)

      keys = keys.flatten.collect { |k| k.to_s }

      Hash[*call_misc('getlist', Rufus::Tokyo::List.new(keys))]
    end

    alias :mget :lget

    # Merges the given hash into this Cabinet (or Tyrant) and returns self.
    #
    def merge! (hash)

      call_misc(
        'putlist',
        hash.inject(Rufus::Tokyo::List.new) { |l, (k, v)|
          l << k.to_s
          l << v.to_s
          l
        })

      self
    end
    alias :lput :merge!

    # Given a list of keys, deletes all the matching entries (in one sweep).
    #
    def ldelete (*keys)

      call_misc(
        'outlist',
        Rufus::Tokyo::List.new(keys.flatten.collect { |k| k.to_s }))
    end

    # Increments the value stored under the given key with the given increment
    # (defaults to 1 (integer)).
    #
    # Accepts an integer or a double value.
    #
    # Warning : Tokyo Cabinet/Tyrant doesn't store counter values as regular
    # strings (db['key'] won't yield something that replies properly to #to_i)
    #
    # Use #counter_value(k) to get the current value set for the counter.
    #
    def incr (key, inc=1)

      key = key.to_s

      v = inc.is_a?(Fixnum) ?
        lib.addint(@db, key, Rufus::Tokyo.blen(key), inc) :
        lib.adddouble(@db, key, Rufus::Tokyo.blen(key), inc)

      raise(TokyoError.new(
        "incr failed, there is probably already a string value set " +
        "for the key '#{key}'. Make sure there is no value before incrementing"
      )) if v == Rufus::Tokyo::INT_MIN || (v.respond_to?(:nan?) && v.nan?)

      v
    end
    alias :addint :incr
    alias :adddouble :incr
    alias :add_int :incr
    alias :add_double :incr

    # Returns the current value for a counter (a float or an int).
    #
    # See #incr
    #
    def counter_value (key)

      incr(key, 0.0) rescue incr(key, 0)
    end

    # Triggers a defrag run (TC >= 1.4.21 only)
    #
    def defrag

      raise(NotImplementedError.new(
        "method defrag is supported since Tokyo Cabinet 1.4.21. " +
        "your TC version doesn't support it"
      )) unless lib.respond_to?(:tctdbsetdfunit)

      call_misc('defrag', Rufus::Tokyo::List.new)
    end

    # Warning : this method is low-level, you probably only need
    # to use #transaction and a block.
    #
    # Direct call for 'transaction begin'.
    #
    def tranbegin

      #check_transaction_support

      libcall(:tcadbtranbegin)
    end

    # Warning : this method is low-level, you probably only need
    # to use #transaction and a block.
    #
    # Direct call for 'transaction commit'.
    #
    def trancommit

      #check_transaction_support

      libcall(:tcadbtrancommit)
    end

    # Warning : this method is low-level, you probably only need
    # to use #transaction and a block.
    #
    # Direct call for 'transaction abort'.
    #
    def tranabort

      #check_transaction_support

      libcall(:tcadbtranabort)
    end

    #--
    #
    # BTREE methods
    #
    #++

    # This is a B+ Tree method only, puts a value for a key who has
    # [potentially] multiple values.
    #
    def putdup (k, v)

      lib.tcbdbputdup(
        as_btree, k, Rufus::Tokyo.blen(k), v, Rufus::Tokyo.blen(v))
    end

    # This is a B+ Tree method only, returns all the values for a given
    # key.
    #
    def get4 (k)

      l = lib.tcbdbget4(as_btree, k, Rufus::Tokyo.blen(k))
      Rufus::Tokyo::List.new(l).release
    end
    alias :getdup :get4

    protected

    # Returns the pointer to the btree hiding behind the abstract structure.
    #
    # Will raise an argument error if the structure behind the abstract db
    # is not a B+ Tree structure.
    #
    def as_btree

      raise(NoMethodError.new("cannot call B+ Tree function on #{@path}")) \
        if ! @path.match(/\.tcb$/)

      lib.tcadbreveal(@db)
    end

    # Returns the pointer to the fixed-width database hiding behind the
    # abstract structure.
    #
    # Will raise an argument error if the structure behind the abstract db
    # is not a fixed-width structure.
    #
    def as_fixed

      raise(NoMethodError.new("cannot call Fixed-width function on #{@path}")) \
        if ! @path.match(/\.tcf$/)

      lib.tcadbreveal(@db)
    end

    # Advanced function. Initially added to uncover tc{b|f|h}dbsetmutex().
    #
    def call_non_abstract_function (funcname, *args)

      lib.send(lib.tcadbreveal(@db), "#{@type}#{funcname}", *args)
    end

    #--
    #def check_transaction_support
    #  raise(TokyoError.new(
    #    "The version of Tokyo Cabinet you're using doesn't support " +
    #    "transactions for non-table structures. Upgrade to TC >= 1.4.13.")
    #  ) unless lib.respond_to?(:tcadbtranbegin)
    #end
    #++

    # Wrapping tcadbmisc or tcrdbmisc
    # (and taking care of freeing the list_pointer)
    #
    def call_misc (function, list_pointer)

      list_pointer = list_pointer.pointer \
        if list_pointer.is_a?(Rufus::Tokyo::List)

      begin
        l = do_call_misc(function, list_pointer)
        raise "function '#{function}' failed" unless l
        Rufus::Tokyo::List.new(l).release
      ensure
        Rufus::Tokyo::List.free(list_pointer)
      end
    end

    # Calls the tcadbmisc function
    #
    def do_call_misc (function, list_pointer)

      lib.tcadbmisc(@db, function, list_pointer)
    end

    def libcall (lib_method, *args)

      raise TokyoError.new("call to #{lib_method} failed") \
        unless lib.send(lib_method, @db, *args)
    end

  end
end


#--
# Copyright (c) 2009-2010, Adam Keys, John Mettraux
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Made in Japan.
#++

module Rufus::Tokyo

  module Ext

    # Calls a lua embedded function
    # (http://tokyocabinet.sourceforge.net/tyrantdoc/#luaext)
    #
    # Options are :global_locking and :record_locking
    #
    # Returns the return value of the called function.
    #
    # Nil is returned in case of failure.
    #
    def ext (func_name, key='', value='', opts={})

      k = key.to_s
      v = value.to_s

      outlen_op(
        :tcrdbext,
        func_name.to_s,
        compute_ext_opts(opts),
        k, Rufus::Tokyo.blen(k),
        v, Rufus::Tokyo.blen(v))
    end
  end
end


#--
# Copyright (c) 2009-2010, John Mettraux, jmettraux@gmail.com
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Made in Japan.
#++

require 'rufus/tokyo/hmethods'

module Rufus::Tokyo

  #
  # :nodoc:
  #
  module ListMapMixin

    # A shortcut
    #
    def clib

      CabinetLib
    end

    # Returns the underlying 'native' (FFI) memory pointer
    #
    def pointer

      @pointer
    end

    def pointer_or_raise

      @pointer || raise("#{self.class} got freed, cannot use anymore")
    end

    def outlen_op (method, *args)

      args.unshift(pointer_or_raise)

      outlen = FFI::MemoryPointer.new(:int)
      args << outlen

      out = clib.send(method, *args)

      return nil if out.address == 0

      out.get_bytes(0, outlen.get_int(0))

    ensure

      outlen.free

      #clib.free(out)
        # uncommenting that wreaks havoc
    end
  end

  #
  # A Tokyo Cabinet in-memory (tcutil.h) map
  #
  # http://tokyocabinet.sourceforge.net/spex-en.html#tcutilapi
  #
  class Map

    include HashMethods
    include ListMapMixin

    # Creates an empty instance of a Tokyo Cabinet in-memory map
    #
    # (It's OK to pass the pointer of a C map directly, this is in fact
    # used in rufus/tokyo/table when retrieving entries)
    #
    def initialize (pointer=nil)

      @pointer = pointer || clib.tcmapnew

      @default_proc = nil
    end

    # Inserts key/value pair
    #
    def []= (k, v)

      clib.tcmapput(pointer, k, Rufus::Tokyo::blen(k), v, Rufus::Tokyo::blen(v))

      v
    end

    # Deletes an entry
    #
    def delete (k)

      v = self[k]
      return nil unless v

      clib.tcmapout(pointer_or_raise, k, Rufus::Tokyo::blen(k)) ||
        raise("failed to remove key '#{k}'")

      v
    end

    # Empties the map
    #
    def clear

      clib.tcmapclear(pointer_or_raise)
    end

    # (the actual #[] method is provided by HashMethods)
    #
    def get (k)

      outlen_op(:tcmapget, k, Rufus::Tokyo.blen(k))
    end
    protected :get

    # Returns an array of all the keys in the map
    #
    def keys

      clib.tcmapiterinit(pointer_or_raise)
      a = []

      klen = FFI::MemoryPointer.new(:int)

      loop do
        k = clib.tcmapiternext(@pointer, klen)
        break if k.address == 0
        a << k.get_bytes(0, klen.get_int(0))
      end

      return a

    ensure

      klen.free
    end

    # Returns the count of entries in the map
    #
    def size

      clib.tcmaprnum(pointer_or_raise)
    end

    alias :length :size

    # Frees the map (nukes it from memory)
    #
    def free

      clib.tcmapdel(pointer_or_raise)
      @pointer = nil
    end

    alias :destroy :free
    alias :close :free

    # Turns a given Tokyo map structure into a Ruby Hash. By default
    # (free = true) will dispose of the map before replying with the Ruby
    # Hash.
    #
    def self.to_h (map_pointer, free=true)

      m = self.new(map_pointer)
      h = m.to_h
      m.free if free

      h
    end

    # Turns a Ruby hash into a Tokyo Cabinet Map and returns it
    # (don't forget to free the map when you're done with it !)
    #
    def self.from_h (h)

      h.inject(Map.new) { |m, (k, v)| m[k] = v; m }
    end

    # Behaves much like Hash#[] but outputs a Rufus::Tokyo::Map
    # (don't forget to free the map when you're done with it !)
    #
    def self.[] (*h_or_a)

      if h_or_a.is_a?(Array) && h_or_a.size == 1 && h_or_a.first.is_a?(Array)
        h_or_a = h_or_a.first
      end

      from_h(::Hash[*h_or_a])
    end
  end

  #
  # A Tokyo Cabinet in-memory (tcutil.h) list
  #
  # http://tokyocabinet.sourceforge.net/spex-en.html#tcutilapi
  #
  class List

    include Enumerable
    include ListMapMixin

    # Creates a new Tokyo Cabinet list.
    #
    # (by passing a list pointer, one can wrap an existing list pointer
    # into a handy instance of this class)
    #
    def initialize (list_pointer=nil)

      if list_pointer.is_a?(FFI::Pointer)
        @pointer = list_pointer
      else
        @pointer = clib.tclistnew
        list_pointer.each { |e| self << e } if list_pointer
      end
    end

    # Inserts an element in the list (note that the lib will raise an
    # ArgumentError if s is not a String)
    #
    def << (s)

      clib.tclistpush(@pointer, s, Rufus::Tokyo.blen(s))

      self
    end

    # Pushes an argument or a list of arguments to this list
    #
    def push (*args)

      args.each { |a| self << a }

      self
    end

    # Pops the last element in the list
    #
    def pop

      outlen_op(:tclistpop)
    end

    # Removes and returns the first element in a list
    #
    def shift

      #clib.tclistshift2(@pointer) rescue nil
      outlen_op(:tclistshift)
    end

    # Inserts a string at the beginning of the list
    #
    def unshift (s)

      clib.tclistunshift(@pointer, s, Rufus::Tokyo.blen(s))

      self
    end

    # The put operation.
    #
    def []= (a, b, c=nil)

      i, s = c.nil? ? [ a, b ] : [ [a, b], c ]

      range = if i.is_a?(Range)
        i
      elsif i.is_a?(Array)
        start, count = i
        (start..start + count - 1)
      else
        [ i ]
      end

      range = norm(range)

      values = s.is_a?(Array) ? s : [ s ]
      # not "values = Array(s)"

      range.each_with_index do |offset, index|
        val = values[index]
        if val
          clib.tclistover(@pointer, offset, val, Rufus::Tokyo.blen(val))
        else
          outlen_op(:tclistremove, values.size)
        end
      end

      self
    end

    # Removes the value at a given index and returns the value
    # (returns nil if no value available)
    #
    def delete_at (i)

      outlen_op(:tclistremove, i)
    end

    def delete_if
      # TODO
    end

    def slice
      # TODO
    end
    def slice!
      # TODO
    end

    # Returns the size of this Tokyo Cabinet list
    #
    def size

      clib.tclistnum(@pointer)
    end

    alias :length :size

    # The equivalent of Ruby Array#[]
    #
    def [] (i, count=nil)

      return nil if (count != nil) && count < 1

      len = self.size

      range = if count.nil?
        i.is_a?(Range) ? i : [i]
      else
        (i..i + count - 1)
      end

      r = norm(range).collect { |ii| outlen_op(:tclistval, ii) }

      range.first == range.last ? r.first : r
    end

    # Empties the list.
    #
    def clear

      clib.tclistclear(@pointer)
    end

    # The classical each.
    #
    def each

      (0..self.size - 1).each { |i| yield self[i] }
    end

    # Turns this Tokyo Cabinet list into a Ruby array
    #
    def to_a

      @pointer.address.zero? ? nil : self.collect { |e| e }
    end

    # Closes (frees) this list
    #
    def free

      self.class.free(@pointer)
      @pointer = nil
    end

    alias :close :free
    alias :destroy :free

    # Frees (closes) the given 'native' (FFI) list (memory pointer)
    #
    def self.free (list_pointer)

      return if list_pointer.address.zero?

      CabinetLib.tclistdel(list_pointer)
    end

    # Closes (frees memory from it) this list and returns the ruby version
    # of it
    #
    def release

      a = self.to_a
      self.close
      a
    end

    # Turns a list pointer into a Ruby Array instance (and makes sure to
    # release the pointer
    #
    def self.release (list_pointer)

      Rufus::Tokyo::List.new(list_pointer).release
    end

    protected

    # Makes sure this offset/range fits the size of the list
    #
    def norm (i)
      l = self.length
      case i
      when Range then ((i.first % l)..(i.last % l))
      when Array then [ i.first % l ]
      else i % l
      end
    end
  end
end


#--
# Copyright (c) 2009-2010, John Mettraux, jmettraux@gmail.com
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Made in Japan.
#++

require 'rufus/tokyo/utils'
require 'rufus/tokyo/query'
require 'rufus/tokyo/config'
require 'rufus/tokyo/transactions'
require 'rufus/tokyo/openable'

module Rufus::Tokyo

  #
  # A 'table' a table database.
  #
  #   http://alpha.mixi.co.jp/blog/?p=290
  #   http://tokyocabinet.sourceforge.net/spex-en.html#tctdbapi
  #
  # A short example :
  #
  #   require 'rubygems'
  #   require 'rufus/tokyo/cabinet/table'
  #
  #   t = Rufus::Tokyo::Table.new('table.tct')
  #
  #   t['pk0'] = { 'name' => 'alfred', 'age' => '22' }
  #   t['pk1'] = { 'name' => 'bob', 'age' => '18' }
  #   t['pk2'] = { 'name' => 'charly', 'age' => '45' }
  #   t['pk3'] = { 'name' => 'doug', 'age' => '77' }
  #   t['pk4'] = { 'name' => 'ephrem', 'age' => '32' }
  #
  #   p t.query { |q|
  #     q.add_condition 'age', :numge, '32'
  #     q.order_by 'age'
  #     q.limit 2
  #   }
  #     # => [ {"name"=>"ephrem", :pk=>"pk4", "age"=>"32"},
  #     #      {"name"=>"charly", :pk=>"pk2", "age"=>"45"} ]
  #
  #   t.close
  #
  class Table

    include HashMethods
    include CabinetConfig
    extend  Openable

    include Transactions
      # this class has tranbegin/trancommit/tranabort so let's include the
      # transaction mixin

    # Creates a Table instance (creates or opens it depending on the args)
    #
    # For example,
    #
    #   t = Rufus::Tokyo::Table.new('table.tct')
    #     # '.tct' suffix is a must
    #
    # will create the table.tct (or simply open it if already present)
    # and make sure we have write access to it.
    #
    # == parameters
    #
    # Parameters can be set in the path or via the optional params hash (like
    # in Rufus::Tokyo::Cabinet)
    #
    #   * :mode    a set of chars ('r'ead, 'w'rite, 'c'reate, 't'runcate,
    #              'e' non locking, 'f' non blocking lock), default is 'wc'
    #   * :opts    a set of chars ('l'arge, 'd'eflate, 'b'zip2, 't'cbs)
    #              (usually empty or something like 'ld' or 'lb')
    #
    #   * :bnum    number of elements of the bucket array
    #   * :apow    size of record alignment by power of 2 (defaults to 4)
    #   * :fpow    maximum number of elements of the free block pool by
    #              power of 2 (defaults to 10)
    #   * :mutex   when set to true, makes sure only 1 thread at a time
    #              accesses the table (well, Ruby, global thread lock, ...)
    #
    #   * :rcnum   specifies the maximum number of records to be cached.
    #              If it is not more than 0, the record cache is disabled.
    #              It is disabled by default.
    #   * :lcnum   specifies the maximum number of leaf nodes to be cached.
    #              If it is not more than 0, the default value is specified.
    #              The default value is 2048.
    #   * :ncnum   specifies the maximum number of non-leaf nodes to be
    #              cached. If it is not more than 0, the default value is
    #              specified. The default value is 512.
    #
    #   * :xmsiz   specifies the size of the extra mapped memory. If it is
    #              not more than 0, the extra mapped memory is disabled.
    #              The default size is 67108864.
    #
    #   * :dfunit  unit step number. If it is not more than 0,
    #              the auto defragmentation is disabled. (Since TC 1.4.21)
    #
    # Some examples :
    #
    #   t = Rufus::Tokyo::Table.new('table.tct')
    #   t = Rufus::Tokyo::Table.new('table.tct#mode=r')
    #   t = Rufus::Tokyo::Table.new('table.tct', :mode => 'r')
    #   t = Rufus::Tokyo::Table.new('table.tct#opts=ld#mode=r')
    #   t = Rufus::Tokyo::Table.new('table.tct', :opts => 'ld', :mode => 'r')
    #
    def initialize (path, params={})

      conf = determine_conf(path, params, :table)

      @db = lib.tctdbnew

      #
      # tune table

      libcall(:tctdbsetmutex) if conf[:mutex]

      libcall(:tctdbtune, conf[:bnum], conf[:apow], conf[:fpow], conf[:opts])

      # TODO : set indexes here... well, there is already #set_index
      #conf[:indexes]...

      libcall(:tctdbsetcache, conf[:rcnum], conf[:lcnum], conf[:ncnum])

      libcall(:tctdbsetxmsiz, conf[:xmsiz])

      libcall(:tctdbsetdfunit, conf[:dfunit]) \
        if lib.respond_to?(:tctdbsetdfunit) # TC >= 1.4.21

      #
      # open table

      @path = conf[:path]

      libcall(:tctdbopen, @path, conf[:mode])

      #
      # no default

      @default_proc = nil
    end

    # Using the cabinet lib
    #
    def lib

      CabinetLib
    end

    # Returns the path to the table.
    #
    def path

      @path
    end

    # Closes the table (and frees the datastructure allocated for it),
    # returns true in case of success.
    #
    def close

      result = lib.tab_close(@db)
      lib.tab_del(@db)

      result
    end

    # Generates a unique id (in the context of this Table instance)
    #
    def generate_unique_id

      lib.tab_genuid(@db)
    end
    alias :genuid :generate_unique_id

    INDEX_TYPES = {
      :lexical => 0,
      :decimal => 1,
      :token => 2,
      :qgram => 3,
      :opt => 9998,
      :optimized => 9998,
      :void => 9999,
      :remove => 9999,
      :keep => 1 << 24
    }

    # Sets an index on a column of the table.
    #
    # Types maybe be :lexical or :decimal.
    #
    # Recently (TC 1.4.26 and 1.4.27) inverted indexes have been added,
    # they are :token and :qgram. There is an :opt index as well.
    #
    # Sorry couldn't find any good doc about those inverted indexes apart from :
    #
    #   http://alpha.mixi.co.jp/blog/?p=1147
    #   http://www.excite-webtl.jp/world/english/web/?wb_url=http%3A%2F%2Falpha.mixi.co.jp%2Fblog%2F%3Fp%3D1147&wb_lp=JAEN&wb_dis=2&wb_submit=+%96%7C+%96%F3+
    #
    # Use :keep to "add" and
    # :remove (or :void) to "remove" an index.
    #
    # If column_name is :pk or "", the index will be set on the primary key.
    #
    # Returns true in case of success.
    #
    def set_index (column_name, *types)

      column_name = column_name == :pk ? '' : column_name.to_s

      ii = types.inject(0) { |i, t| i = i | INDEX_TYPES[t]; i }

      lib.tab_setindex(@db, column_name, ii)
    end

    # Inserts a record in the table db
    #
    #   table['pk0'] = [ 'name', 'fred', 'age', '45' ]
    #   table['pk1'] = { 'name' => 'jeff', 'age' => '46' }
    #
    # Accepts both a hash or an array (expects the array to be of the
    # form [ key, value, key, value, ... ] else it will raise
    # an ArgumentError)
    #
    # Raises an error in case of failure.
    #
    def []= (pk, h_or_a)

      pk = pk.to_s
      h_or_a = Rufus::Tokyo.h_or_a_to_s(h_or_a)

      m = Rufus::Tokyo::Map[h_or_a]

      r = lib.tab_put(@db, pk, Rufus::Tokyo.blen(pk), m.pointer)

      m.free

      r || raise_error # raising potential error after freeing map

      h_or_a
    end

    # Removes an entry in the table
    #
    # (might raise an error if the delete itself failed, but returns nil
    # if there was no entry for the given key)
    #
    def delete (k)

      k = k.to_s

      v = self[k]
      return nil unless v
      libcall(:tab_out, k, Rufus::Tokyo.blen(k))

      v
    end

    # Removes all records in this table database
    #
    def clear

      libcall(:tab_vanish)
    end

    # Returns an array of all the primary keys in the table
    #
    # With no options given, this method will return all the keys (strings)
    # in a Ruby array.
    #
    #   :prefix --> returns only the keys who match a given string prefix
    #
    #   :limit --> returns a limited number of keys
    #
    #   :native --> returns an instance of Rufus::Tokyo::List instead of
    #     a Ruby Hash, you have to call #free on that List when done with it !
    #     Else you're exposing yourself to a memory leak.
    #
    def keys (options={})

      pre = options.fetch(:prefix, "")

      l = lib.tab_fwmkeys(
        @db, pre, Rufus::Tokyo.blen(pre), options[:limit] || -1)

      l = Rufus::Tokyo::List.new(l)

      options[:native] ? l : l.release
    end

    # Deletes all the entries whose key begin with the given prefix.
    #
    def delete_keys_with_prefix (prefix)

      query_delete { |q| q.add('', :strbw, prefix) }
    end

    # No 'misc' methods for the table library, so this lget is equivalent
    # to calling get for each key. Hoping later versions of TC will provide
    # a mget method.
    #
    def lget (*keys)

      keys.flatten.inject({}) { |h, k|
        k = k.to_s
        v = self[k]
        h[k] = v if v
        h
      }
    end

    alias :mget :lget

    # Returns the number of records in this table db
    #
    def size

      lib.tab_rnum(@db)
    end

    # Prepares a query instance (block is optional)
    #
    def prepare_query (&block)

      q = TableQuery.new(self)
      block.call(q) if block

      q
    end

    # Prepares and runs a query, returns a ResultSet instance
    # (takes care of freeing the query structure)
    #
    def do_query (&block)

      q = prepare_query(&block)
      rs = q.run

      return rs

    ensure
      q && q.free
    end

    # Prepares and runs a query, returns an array of hashes (all Ruby)
    # (takes care of freeing the query and the result set structures)
    #
    def query (&block)

      rs = do_query(&block)
      a = rs.to_a

      return a

    ensure
      rs && rs.free
    end

    # Prepares a query and then runs it and deletes all the results.
    #
    def query_delete (&block)

      q = prepare_query(&block)
      rs = q.delete

      return rs

    ensure
      q && q.free
    end

    # Prepares a query and then runs it and deletes all the results.
    #
    def query_count (&block)

      q = prepare_query { |q|
        q.pk_only  # improve efficiency, since we have to do the query
      }
      q.count
    ensure
      q.free if q
    end

    # Warning : this method is low-level, you probably only need
    # to use #transaction and a block.
    #
    # Direct call for 'transaction begin'.
    #
    def tranbegin

      libcall(:tctdbtranbegin)
    end

    # Warning : this method is low-level, you probably only need
    # to use #transaction and a block.
    #
    # Direct call for 'transaction commit'.
    #
    def trancommit

      libcall(:tctdbtrancommit)
    end

    # Warning : this method is low-level, you probably only need
    # to use #transaction and a block.
    #
    # Direct call for 'transaction abort'.
    #
    def tranabort

      libcall(:tctdbtranabort)
    end

    # Returns the actual pointer to the Tokyo Cabinet table
    #
    def pointer

      @db
    end

    # Returns the union of the listed queries
    #
    #   r = table.union(
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'es'
    #     },
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'li'
    #     }
    #   )
    #
    # will return a hash { primary_key => record } of the values matching
    # the first query OR the second.
    #
    # If the last element element passed to this method is the value 'false',
    # the return value will the array of matching primary keys.
    #
    def union (*queries)

      search(:union, *queries)
    end

    # Returns the intersection of the listed queries
    #
    #   r = table.intersection(
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'es'
    #     },
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'li'
    #     }
    #   )
    #
    # will return a hash { primary_key => record } of the values matching
    # the first query AND the second.
    #
    # If the last element element passed to this method is the value 'false',
    # the return value will the array of matching primary keys.
    #
    def intersection (*queries)

      search(:intersection, *queries)
    end

    # Returns the difference of the listed queries
    #
    #   r = table.intersection(
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'es'
    #     },
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'li'
    #     }
    #   )
    #
    # will return a hash { primary_key => record } of the values matching
    # the first query OR the second but not both.
    #
    # If the last element element passed to this method is the value 'false',
    # the return value will the array of matching primary keys.
    #
    def difference (*queries)

      search(:difference, *queries)
    end

    # A #search a la ruby-tokyotyrant
    # (http://github.com/actsasflinn/ruby-tokyotyrant/tree)
    #
    #   r = table.search(
    #     :intersection,
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'es'
    #     },
    #     @t.prepare_query { |q|
    #       q.add 'lang', :includes, 'li'
    #     }
    #   )
    #
    # Accepts the symbols :union, :intersection, :difference or :diff as
    # first parameter.
    #
    # If the last element element passed to this method is the value 'false',
    # the return value will the array of matching primary keys.
    #
    def search (type, *queries)

      run_query = true
      run_query = queries.pop if queries.last == false

      raise(
        ArgumentError.new("pass at least one prepared query")
      ) if queries.size < 1

      raise(
        ArgumentError.new("pass instances of Rufus::Tokyo::TableQuery only")
      ) if queries.find { |q| q.class != TableQuery }

      t = META_TYPES[type]

      raise(
        ArgumentError.new("no search type #{type.inspect}")
      ) unless t

      qs = FFI::MemoryPointer.new(:pointer, queries.size)
      qs.write_array_of_pointer(queries.collect { |q| q.pointer })

      r = lib.tab_metasearch(qs, queries.size, t)

      qs.free

      pks = Rufus::Tokyo::List.new(r).release

      run_query ? lget(pks) : pks
    end

    protected

    META_TYPES = {
      :union => 0, :intersection => 1, :difference => 2, :diff => 2
    }

    # Returns the value (as a Ruby Hash) else nil
    #
    # (the actual #[] method is provided by HashMethods)
    #
    def get (k)
      k = k.to_s
      m = lib.tab_get(@db, k, Rufus::Tokyo.blen(k))

      return nil if m.address == 0

      Map.to_h(m) # which frees the map
    end

    def libcall (lib_method, *args)

      lib.send(lib_method, @db, *args) || raise_error
        # stack level too deep with JRuby 1.1.6 :(

      #(eval(%{ lib.#{lib_method}(@db, *args) }) == 1) or raise_error
        # works with JRuby 1.1.6
    end

    # Obviously something got wrong, let's ask the db about it and raise
    # a TokyoError
    #
    def raise_error

      err_code = lib.tab_ecode(@db)
      err_msg = lib.tab_errmsg(err_code)

      raise TokyoError.new("(err #{err_code}) #{err_msg}")
    end
  end

  #
  # A query on a Tokyo Cabinet table db
  #
  class TableQuery

    include QueryConstants

    # Creates a query for a given Rufus::Tokyo::Table
    #
    # Queries are usually created via the #query (#prepare_query #do_query)
    # of the Table instance.
    #
    # Methods of interest here are :
    #
    #   * #add (or #add_condition)
    #   * #order_by
    #   * #limit
    #
    # also
    #
    #   * #pk_only
    #   * #no_pk
    #
    def initialize (table)

      @table   = table
      @query   = @table.lib.qry_new(@table.pointer)
      @opts    = {}
      @has_run = false
    end

    # Returns the FFI lib the table uses.
    #
    def lib

      @table.lib
    end

    # Returns the underlying pointer.
    #
    def pointer

      @query
    end

    # Adds a condition
    #
    #   table.query { |q|
    #     q.add 'name', :equals, 'Oppenheimer'
    #     q.add 'age', :numgt, 35
    #   }
    #
    # Understood 'operators' :
    #
    #   :streq # string equality
    #   :eq
    #   :eql
    #   :equals
    #
    #   :strinc # string include
    #   :inc # string include
    #   :includes # string include
    #
    #   :strbw # string begins with
    #   :bw
    #   :starts_with
    #   :strew # string ends with
    #   :ew
    #   :ends_with
    #
    #   :strand # string which include all the tokens in the given exp
    #   :and
    #
    #   :stror # string which include at least one of the tokens
    #   :or
    #
    #   :stroreq # string which is equal to at least one token
    #
    #   :strorrx # string which matches the given regex
    #   :regex
    #   :matches
    #
    #   # numbers...
    #
    #   :numeq # equal
    #   :numequals
    #   :numgt # greater than
    #   :gt
    #   :numge # greater or equal
    #   :ge
    #   :gte
    #   :numlt # greater or equal
    #   :lt
    #   :numle # greater or equal
    #   :le
    #   :lte
    #   :numbt # a number between two tokens in the given exp
    #   :bt
    #   :between
    #
    #   :numoreq # number which is equal to at least one token
    #
    #   :ftsph # full-text phrase search
    #   :ftsphrase
    #   :phrase
    #   :ftsand # full-text AND
    #   :ftsor # full-text OR
    #   :ftsex # full-text with 'compound' expression
    #
    def add (colname, operator, val, affirmative=true, no_index=false)

      colname = colname.to_s
      val = val.to_s

      op = operator.is_a?(Fixnum) ? operator : OPERATORS[operator]
      op = op | TDBQCNEGATE unless affirmative
      op = op | TDBQCNOIDX if no_index
      lib.qry_addcond(@query, colname, op, val)
    end
    alias :add_condition :add

    # Sets the max number of records to return for this query.
    #
    # (If you're using TC >= 1.4.10 the optional 'offset' (skip) parameter
    # is accepted)
    #
    def limit (i, offset=-1)

      lib.qry_setlimit(@query, i, offset)
    end

    # Sets the sort order for the result of the query
    #
    # The 'direction' may be :
    #
    #   :strasc # string ascending
    #   :strdesc
    #   :asc # string ascending
    #   :desc
    #   :numasc # number ascending
    #   :numdesc
    #
    def order_by (colname, direction=:strasc)

      lib.qry_setorder(@query, colname.to_s, DIRECTIONS[direction])
    end

    # When set to true, only the primary keys of the matching records will
    # be returned.
    #
    def pk_only (on=true)

      @opts[:pk_only] = on
    end

    # When set to true, the :pk (primary key) is not inserted in the record
    # (hashes) returned
    #
    def no_pk (on=true)

      @opts[:no_pk] = on
    end

    # Process each record using the supplied block, which will be passed
    # two parameters, the primary key and the value hash.
    #
    # The block passed to this method accepts two parameters : the [String]
    # primary key and a Hash of the values for the record.
    #
    # The return value of the passed block does matter. Three different
    # values are expected :stop, :delete or a Hash instance.
    #
    # :stop will make the iteration stop, further matching records will not
    # be passed to the block
    #
    # :delete will let Tokyo Cabinet delete the record just seen.
    #
    # a Hash is passed to let TC update the values for the record just seen.
    #
    # Passing an array is possible : [ :stop, { 'name' => 'Toto' } ] will
    # update the record just seen to a unique column 'name' and will stop the
    # iteration. Likewise, returning [ :stop, :delete ] will work as well.
    #
    # (by Matthew King)
    #
    def process (&block)

      callback = lambda do |pk, pklen, map, opt_param|

        key = pk.read_string(pklen)
        val = Rufus::Tokyo::Map.new(map).to_h

        r = block.call(key, val)
        r = [ r ] unless r.is_a?(Array)

        if updated_value = r.find { |e| e.is_a?(Hash) }
          Rufus::Tokyo::Map.new(map).merge!(updated_value)
        end

        r.inject(0) { |i, v|
          case v
          when :stop then i = i | 1 << 24
          when :delete then i = i | 2
          when Hash then i = i | 1
          end
          i
        }
      end

      lib.qry_proc(@query, callback, nil)

      self
    end

    # Runs this query (returns a TableResultSet instance)
    #
    def run

      @has_run = true
      #@last_resultset =
      TableResultSet.new(@table, lib.qry_search(@query), @opts)
    end

    # Runs this query AND let all the matching records get deleted.
    #
    def delete

      lib.qry_searchout(@query) || raise_error
    end

    # Gets the count of records returned by this query.
    #
    # Note : the 'real' impl is only available since TokyoCabinet 1.4.12.
    #
    def count

      #if lib.respond_to?(:qry_count)
      run.free unless @has_run
      lib.qry_count(@query)
      #else
      #  @last_resultset ? @last_resultset.size : 0
      #end
    end

    # Frees this data structure
    #
    def free

      lib.qry_del(@query)
      @query = nil
    end

    alias :close :free
    alias :destroy :free
  end

  #
  # The thing queries return
  #
  class TableResultSet

    include Enumerable

    def initialize (table, list_pointer, query_opts)

      @table = table
      @list = Rufus::Tokyo::List.new(list_pointer)
      @opts = query_opts
    end

    # Returns the count of element in this result set
    #
    def size

      @list.size
    end

    alias :length :size

    # The classical each
    #
    def each

      (0..size-1).each do |i|
        pk = @list[i]
        if @opts[:pk_only]
          yield(pk)
        else
          val = @table[pk]
          val[:pk] = pk unless @opts[:no_pk]
          yield(val)
        end
      end
    end

    # Returns an array of hashes
    #
    def to_a

      collect { |m| m }
    end

    # Frees this query (the underlying Tokyo Cabinet list structure)
    #
    def free

      @list.free
      @list = nil
    end
    alias :close :free
    alias :destroy :free

  end
end


require 'game_icons/db'
require 'game_icons/icon'
require 'game_icons/did_you_mean'

module GameIcons
  class Finder

    # Find the icon, possibly without the extension.
    # @example Finder.new.find('glass-heart')
    # Raises an error if the icon could not be found.
    def find(icon)
      str = icon.to_s.downcase
      file = DB.files[str] ||
               DB.files[str.sub(/\.svg$/,'')] ||
               not_found(str, icon)
      Icon.new(file)
    end

    private
    def not_found(str, orig_str)
      dym = DidYouMean.new(DB.names).query(str)
      raise("game_icons: could not find icon '#{orig_str}'. Did you mean any of these? #{dym.join(', ')}")
    end

  end
end


require 'to-arff/version'
require 'sqlite3'
require 'json'

module ToARFF
  RELATION_MARKER = '@RELATION'.freeze
  ATTRIBUTE_MARKER = '@ATTRIBUTE'.freeze
  DATA_MARKER = '@DATA'.freeze
  ATTRIBUTE_TYPE_NUMERIC = 'NUMERIC'.freeze
  ATTRIBUTE_TYPE_STRING = 'STRING'.freeze

	class SQLiteDB
		attr_accessor :db_file_path, :db, :tables, :columns, :column_type

		def initialize(path)
		  @db_file_path = path
			@tables = []
			@columns = {}
			@column_type = {}
			process_db_file
			set_all_tables
			set_all_columns
		end

		def process_db_file
			if @db_file_path != ''
				if File.exist? "#{@db_file_path}"
					@db = SQLite3::Database.open "#{@db_file_path}"
				else
					raise "#{@db_file_path} doesn't exist. Enter a valid file path."
				end
			else
				raise "Database File Path cannot be empty."
			end
		end

		# Get all the tables' name and store them in an array (@tables).
		def set_all_tables
			tables_arr = @db.execute("SELECT name FROM sqlite_master WHERE type='table';")
			tables_arr.each do |elem|
				@tables.push(elem.first)
			end
		end

		# Get all colums for a given table.
		def get_columns(table_name)
			columns_arr = []
		    pst = @db.prepare "SELECT * FROM #{table_name} LIMIT 6"
		    pst.columns.each do |c|
		    	columns_arr.push(c)
		    end
		  columns_arr
		end

		def set_all_columns
			@tables.each do |t|
				@columns[t] = get_columns(t)
			end
		end

		# If the column type is nominal return true.
		def is_numeric(table_name, column_name)
			if @db.execute("SELECT #{column_name} from #{table_name} LIMIT 1").first.first.is_a? Numeric
				return true
			else
				return false
			end
		end

		# Converts a table to ARFF.
		def convert_table(table_name)
	  	convert_table_with_columns(table_name, get_columns(table_name))
		end

		def write_relation(table_name)
			"#{RELATION_MARKER} #{table_name}\n\n"
		end

		def write_attributes(table_name, columns, column_types)
			rel = ""
			if column_types.nil?
				columns.each do |col|
					if is_numeric(table_name, col)
						rel << "#{ATTRIBUTE_MARKER} #{col} #{ATTRIBUTE_TYPE_NUMERIC}\n"
					else
						rel << "#{ATTRIBUTE_MARKER} #{col} #{ATTRIBUTE_TYPE_STRING}\n"
					end
				end
			else
				columns.each do |col|
					rel << "#{ATTRIBUTE_MARKER} #{col} #{column_types[table_name][col]}\n"
				end
			end
			rel
		end

		def write_data(table_name, columns)
			rel = ''
			columns_str = ''
			columns.each do |col|
				columns_str += col + ', '
			end
			columns_str = columns_str.chomp(', ')
			rel << "\n#{DATA_MARKER}\n"
			data = @db.prepare "SELECT #{columns_str} FROM #{table_name}"
			data.each do |elem|
				row = ''
				elem.each do |val|
					if val.is_a? Numeric
						row = row + "#{val}" + ","
					else
						row = row + "\"#{val}\"" + ","
					end
				end
				rel << row.strip.chomp(",")
				rel << "\n"
			end
			rel << "\n\n\n"
	    rel
		end

		def convert_table_with_columns(table_name, columns, column_types=nil)
			rel = write_relation(table_name)
			rel << write_attributes(table_name, columns, column_types)
			rel << write_data(table_name, columns)
			rel
		end

		def convert_from_columns_hash(cols_hash)
			rel = ""
			cols_hash.keys.each do |table|
				rel << convert_table_with_columns(table, cols_hash[table])
			end
			rel
		end

		def convert_from_column_types_hash(col_types_hash)
			rel = ""
			col_types_hash.keys.each do |table|
				rel << convert_table_with_columns(table, col_types_hash[table].keys, col_types_hash)
			end
			rel
		end

		def check_given_tables_validity(given_tables)
			dif = downcase_array(given_tables) - downcase_array(@tables)
			if !dif.empty?		# If @tables doesn't contain all elements of given_tables
				raise ArgumentError.new("\"#{dif.first}\" does not exist.")
			end
		end

		def check_given_columns_validity(given_columns)
			given_tables = given_columns.keys
			check_given_tables_validity(given_tables)
			given_tables.each do |elem|
				dif = downcase_array(given_columns[elem]) - downcase_array(@columns[elem])
				if !dif.empty?		# If @tables doesn't contain all elements of given_tables
					raise ArgumentError.new("\"#{dif.first}\" does not exist.")
				end
			end
		end

		def downcase_array(arr)
			downcased_array = Array.new
			arr.each do |elem|
				if elem.is_a? String
					downcased_array.push(elem.downcase)
				elsif elem.is_a? Array
					downcased_array.push(elem.first.downcase)
				end
			end
			downcased_array
		end

		def valid_option_given(options)
			return options.keys.first.to_s != "tables" && options.keys.first.to_s != "columns" && options.keys.first.to_s != "column_types"
		end

		# If valid option was provided in convert method
		def deal_with_valid_option(temp_tables, temp_columns, temp_column_types, res)
			if !temp_tables.empty?
				check_given_tables_validity(temp_tables)
				temp_tables.each do |t|
					res << convert_table(t)
				end
			elsif !temp_columns.keys.empty?
				check_given_columns_validity(temp_columns)
				res << convert_from_columns_hash(temp_columns)
			elsif !temp_column_types.empty?
				check_given_columns_validity(temp_column_types)
				res << convert_from_column_types_hash(temp_column_types)
			end
		end

	def stringify_all_keys(hash)
	  stringified_hash = {}
	  hash.each do |k, v|
	    stringified_hash[k.to_s] = v.is_a?(Hash) ? stringify_all_keys(v) : v
	  end
	  stringified_hash
	end

		def convert(options={})
			temp_tables = options.fetch(:tables, Array.new)
			temp_columns = options.fetch(:columns, Hash.new)
			temp_column_types = options.fetch(:column_types, Hash.new)
			res = ""
			param_count = options.keys.length
			if param_count == 0
				@tables.each do |t|
					res << convert_table(t)
				end
			elsif param_count == 1
				if valid_option_given(options)
					raise ArgumentError.new("Wrong parameter name \":#{options.keys.first}\"")
				else
					deal_with_valid_option(temp_tables, stringify_all_keys(temp_columns), stringify_all_keys(temp_column_types), res)
				end
			elsif param_count > 1
				raise ArgumentError.new("You can specify only one out of the three parameters: table, columns, column_types.")
			end
			res
		end

	end

end