# frozen_string_literal: true
# This extends +Coursemology::Polyglot::Language+ to support integration into a database.
#
# Each concrete language has a unique +Polyglot::Language.instance+ which is internally called the
# +root_instance+ of the language.
#
# Do *NOT* remove languages after they have been defined because a database record corresponds to
# a class implemented here.
module Extensions::PolyglotWithDatabase::Coursemology::Polyglot::Language
  extend ActiveSupport::Concern

  included do
    self.table_name = 'polyglot_languages'
    acts_as_forest optional: true

    after_initialize :set_readonly
    after_save :set_readonly

    validate :unique_root_language, unless: :parent

    # @!method self.with_language(languages)
    #   Gets all languages in the given set.
    #
    #   @param [Array<String>] languages
    scope :with_language, (lambda do |languages|
      if languages.blank?
        all
      else
        where(name: languages)
      end
    end)
  end

  module ClassMethods
    # Loads or creates the database records for each defined programming language.
    def load_languages
      concrete_languages.each(&:instance)
    end

    private

    # Finds or creates the root instance for languages of this class.
    #
    # @return [Coursemology::Polyglot::Language]
    def root_instance
      # Creating the root record would call +root_instance+, so if we are defining @root, we return
      # nil.
      if instance_variable_defined?(:@root_instance)
        @root_instance
      else
        @root_instance = nil
        @root_instance = find_or_create_by(name: display_name, parent: nil)
      end
    end
  end

  private

  # Sets the record as readonly if this is the root record
  def set_readonly
    readonly! if persisted? && !parent
  end

  # Ensures that only one of each type of language has no parent.
  def unique_root_language
    errors.add(:parent, :taken) if self.class.send(:root_instance)
  end
end

module Coursemology::Polyglot::ConcreteLanguage
  # Returns language name in lowercase format (eg python, java).
  #
  # @return [String] The language name in lowercase format.
  def polyglot_name
    name.split[0].downcase
  end

  # Returns language version.
  #
  # @return [String] The language version.
  def polyglot_version
    name.split[1]
  end

  # This directly injects a method into all concrete languages' class methods.
  module ClassMethods
    def instance
      root_instance
    end
  end
end
