# encoding: UTF-8


require 'base58'

module Spontaneous::Schema
  class UIDMap

    def self.uid_lock
      @uid_lock ||= Mutex.new
    end

    def self.uid_index
      @uid_index ||= 0
    end

    def self.increment_uid_index
      @uid_index = (uid_index + 1) % 0xFFFF
    end

    def self.get_inc
      uid_lock.synchronize do
        increment_uid_index
      end
    end

    def self.generate(ref = nil)
      generate58(ref)
    end

    def self.generate58(ref)
      # reverse the time so that sequential ids are more obviously different
      oid =  Base58.encode((Time.now.to_f * 1000).to_i).reverse
      oid << Base58.encode(get_inc).rjust(3, '0')
    end

    def self.generate16(ref)
      oid = ''
      # 4 bytes current time
      oid = (Time.now.to_f * 1000).to_i.to_s(16)
      # 2 bytes inc
      oid << get_inc.to_s(16).rjust(4, '0')
    end

    include Enumerable

    def initialize
      @instance_lock  = Mutex.new
      @instances = {}
    end

    def load(id, reference)
      @instance_lock.synchronize do
        unless instance = @instances[id]
          instance = UID.new(self, id, reference)
          # check to make sure that the uid is valid first
          # this ensures that UIDs are not created before their owners
          # have uids
          if instance.valid?
            @instances[id] = instance
          else
            instance = nil
          end
        end
        instance
      end
    end

    def create(reference)
      unless instance = @instances.values.detect { |i| i.reference == reference }
        instance = load(self.class.generate(reference), reference)
      end
      instance
    end

    def existing_references
      @instances.values.map { |i| i.reference }
    end

    def create_for(obj)
      id = create(obj.schema_name)
      obj.update_schema_id(id) if obj.respond_to?(:update_schema_id)
    end

    def destroy(uid)
      @instance_lock.synchronize do
        @instances.delete(uid.to_s)
        uid.after_destroy
      end
    end

    def clear!
      @instances = {}
    end

    def [](id)
      return id if UID === id
      return nil if id.blank?
      @instances[id]
    end

    def get_id(reference)
      self.find { |uid| uid.reference == reference }
    end

    def each
      uids = @instances.map { |id, instance| instance }
      if block_given?
        uids.each { |instance| yield(instance) }
      else
        uids.each
      end
    end


    def export
      Hash[ @instances.map { |id, ref| [id, ref.reference] } ]
    end
  end # UIDMap
end