class SupplierEmployeesSettings
  attr_reader :dictionary, :employee_ids, :supplier
  def initialize(supplier)
    @supplier = supplier
    @dictionary = (supplier.employee_settings_storage || {}).to_h
    @employee_ids = supplier.employee_ids || []
  end

  def for_employee(employee)
    return SupplierEmployeeSettings::NullObject.new unless employee.present?
    if employee_ids.include? employee.id
      settings = SupplierEmployeeSettings.new(self, employee, dictionary[employee.id])
    else
      settings = SupplierEmployeeSettings::NullObject.new
    end
    employee.settings = settings
  end

  def [](val)
    val = val.to_s
    if dictionary[val].is_a? SettingsPersistor
      dictionary[val]
    else
      dictionary[val] = SettingsPersistor.new(self, dictionary[val])
    end
  end

  def to_store!
    supplier.employee_settings_storage = dictionary
  end

  def method_missing(m, *args)
    dictionary.send(m, *args)
  end

  class SettingsPersistor < Hash
    attr_reader :all_employees_settings
    def initialize(all_employees_settings, options = {})
      @all_employees_settings = all_employees_settings
      self.replace options if options.is_a? Hash
    end

    alias_method :orig_setter, :[]=
    def []=(*args)
      orig_setter(*args)
      all_employees_settings.to_store!
    end
  end

  class SupplierEmployeeSettings
    delegate :as_json, :to_json, to: :settings
    attr_reader :employee, :settings, :all_employees_settings
    class NullObject < ::NullObject
      ::Employee::DEFAULT_SETTINGS.each do |attribute, value|
        if value == true or value == false
          define_method("#{attribute}?"){ value }
        end
        define_method(attribute){ value }
      end
      def public_send(m, *args)
        if ::Employee::DEFAULT_SETTINGS.has_key?(m.to_s)
          ::Employee::DEFAULT_SETTINGS[m.to_s]
        else
          nil # this time we will raise on further invocation, since then there is something wrong
        end
      end

      def as_json
        {}
      end
    end

    def initialize(all_employees_settings, employee, settings = {})
      @all_employees_settings = all_employees_settings
      @employee = employee
      @settings = Employee.default_settings.merge(settings || {})
    end

    def [](val)
      settings[val.to_s]
    end

    def set(attribute, value, persist: false)
      settings[attribute] = value
      all_employees_settings[employee.id][attribute] = value
      persist! if persist
      self
    end

    def []=(attribute, value)
      set attribute, value
    end

    def set!(attribute, value)
      set attribute, value, persist: true
    end

    def update(params)
      Employee::DEFAULT_SETTINGS.keys.each do |attribute|
        if params.has_key?(attribute)
          new_value = params.delete(attribute)
          set attribute, new_value
        end
      end
      params
    end

    def update!(params)
      update params
      persist!
      params
    end

    def persist!
      all_employees_settings.supplier.is_dirty
      all_employees_settings.supplier.save
    end

    def supplier
      all_employees_settings.supplier
    end

    # Parse a method name to its underlying operations
    #   settings.is_manager?
    # is a getter for the attribute manager
    #   settings.is_manager!
    # is a boolean setter to true for the attribute manager and persists the inderlying model
    # without exclamation mark it is the same, but without persisting the model.
    #   settings.is_not_manager!
    # is a boolean setter to false for the attribute manger.
    #   settings.manager
    # is a normal accessor for the attribute manager
    def parse_method_for_boolean_operation(method)
      method = method.to_s
      persist = false
      attribute = method
      set_value = nil
      if method.end_with? '!'
        persist = true
        set_value = true
        attribute.chop!
      end
      if method.end_with? '?'
        # Always a getter, so no set_value
        attribute.chop!
        #attribute = attribute.from(7) if attribute.start_with? 'is_not'
        attribute = attribute.from(3) if attribute.start_with? 'is_'
      elsif method.start_with? 'is_not_'
        set_value = false
        attribute = attribute.from 7
      elsif method.start_with? 'is_'
        set_value = true
        attribute = attribute.from 3
      end
      raise "Settings do not support attribute '#{attribute}'" unless Employee.default_settings.has_key?(attribute) # Do not return a nonexistent attribute
      [attribute, set_value, persist]
    end

    def method_missing(m, *args)
      attribute, set_value, persist = parse_method_for_boolean_operation(m)
      return unless attribute # method cannot be used in the settings
      return settings[attribute] if set_value.nil? # Getter operation, setting nil is not supported
      set attribute, set_value, persist: persist
    end
  end
end
