module DataMapper
  module Adapters
    module TokyoCabinet
      #--
      # TODO: Lots of duplicate code to be golfed out.
      class Adapter < AbstractAdapter
        require 'fileutils'

        def initialize(name, uri_or_options)
          super
          @path = FileUtils.mkdir_p(@uri[:path] / @uri[:database])
        end

        def create(resources)
          resources.map do |resource|
            with_connection(resource.model) do |connection|
              if identity_field = resource.model.key(name).detect{|p| p.serial?}
                identity_field.set!(resource, connection.genuid)
              end

              # TODO: I'm kinda making this up as I go, pretty sure get! isn't what I want here.
              store = resource.model.properties.inject({}) do |accumulator, property|
                accumulator[property.field(name)] = property.get!(resource).to_s
                accumulator
              end

              values = resource.model.key(name).map{|property| property.get!(resource).to_s}
              connection.put(
                (values.size > 1 ? Digest::SHA1.hexdigest(values.join(':')) : values.first),
                store
              ) || nil
            end
          end.compact.size
        end

        def update(attributes, query)
          read_many(query).map do |resource|
            attributes.each do |property, value|
              property.set!(resource, value)
            end

            # TODO: I'm kinda making this up as I go, pretty sure get! isn't what I want here.
            store = resource.model.properties.inject({}) do |accumulator, property|
              accumulator[property.field(name)] = property.get!(resource).to_s
              accumulator
            end

            with_connection(resource.model) do |connection|
              values = resource.model.key(name).map{|property| property.get!(resource).to_s}
              connection.put(
                (values.size > 1 ? Digest::SHA1.hexdigest(values.join(':')) : values.first),
                store
              ) || nil
            end
          end.compact.size
        end

        def delete(query)
          with_connection(query.model) do |connection|
            if query.conditions.size == 0
              records = connection.rnum
              connection.vanish
              return records
            end

            tc_query = TokyoCabinet::Query.new(connection, query)
            results  = tc_query.search
            keys     = query.model.key(name)

            results.each do |result|
              values = keys.map{|key| result[key.field(name)]}
              connection.out(values.size > 1 ? Digest::SHA1.hexdigest(values.join(':')) : values.first)
            end.compact.size
          end
        end

        def read_one(query)
          read(query, query.model, false)
        end

        def read_many(query)
          Collection.new(query) do |set|
            read(query, set, true)
          end
        end

        protected
          def read(query, set, many = true)
            with_connection(query.model) do |connection|
              tc_query = TokyoCabinet::Query.new(connection, query)
              results  = tc_query.search

              results.each do |result|
                next if result.nil?
                values = query.model.properties.map do |property|
                  property.typecast(result[property.field(name)])
                end
                many ? set.load(values) : (break set.load(values, query))
              end
            end
          end

          def with_connection(model)
            # TODO: Mode option.
            tdb  = ::TokyoCabinet::TDB.new
            mode = ::TokyoCabinet::TDB::OWRITER | ::TokyoCabinet::TDB::OCREAT
            if tdb.open(@path / model.storage_name(name) + '.tdb', mode)
              begin
                return yield(tdb)
              ensure
                tdb.close
              end
            else
              raise "TokyoCabinet Error: #{tdb.ecode}"
            end
          end
      end # Adapter
    end # TokyoCabinet

    # Keep our classes nice and organized while still meeting the DM load magic happy.
    TokyoCabinetAdapter = TokyoCabinet::Adapter
  end # Adapters
end # DataMapper