require 'leveldb-native'

module Bitcoin
  module Store
    module DB

      class LevelDB

        attr_reader :db
        attr_reader :logger

        def initialize(path = "#{Bitcoin.base_dir}/db/spv")
          # @logger = Bitcoin::Logger.create(:debug)
          FileUtils.mkdir_p(path)
          @db = ::LevelDBNative::DB.new(path)
          # logger.debug 'Opened LevelDB successfully.'
        end

        # put data into LevelDB.
        # @param [Object] key a key.
        # @param [Object] value a value.
        def put(key, value)
          # logger.debug "put #{key} data"
          db.put(key, value)
        end

        # get value from specified key.
        # @param [Object] key a key.
        # @return[Object] the stored value.
        def get(key)
          db.get(key)
        end

        # get best block hash.
        def best_hash
          db.get(KEY_PREFIX[:best])
        end

        # delete specified key data.
        def delete(key)
          db.delete(key)
        end

        # get block hash specified +height+
        def get_hash_from_height(height)
          db.get(height_key(height))
        end

        # get next block hash specified +hash+
        def next_hash(hash)
          db.get(KEY_PREFIX[:next] + hash)
        end

        # get entry payload
        # @param [String] hash the hash with hex format.
        # @return [String] the ChainEntry payload.
        def get_entry_payload_from_hash(hash)
          db.get(KEY_PREFIX[:entry] + hash)
        end

        def save_entry(entry)
          db.batch do
            db.put(entry.key ,entry.to_payload)
            db.put(height_key(entry.height), entry.block_hash)
            connect_entry(entry)
          end
        end

        def close
          db.close
        end

        private

        # generate height key
        def height_key(height)
          height = height.to_even_length_hex
          KEY_PREFIX[:height] + height.rhex
        end

        def connect_entry(entry)
          unless entry.genesis?
            tip_block = Bitcoin::Store::ChainEntry.parse_from_payload(get_entry_payload_from_hash(best_hash))
            unless tip_block.block_hash == entry.prev_hash
              raise "entry(#{entry.block_hash}) does not reference current best block hash(#{tip_block.block_hash})"
            end
            unless tip_block.height + 1 == entry.height
              raise "block height is small than current best block."
            end
          end
          db.put(KEY_PREFIX[:best], entry.block_hash)
          db.put(KEY_PREFIX[:next] + entry.prev_hash, entry.block_hash)
        end
      end

    end
  end
end
