# frozen_string_literal: true
if defined?(ActiveRecord)
  # :nocov:
  # TODO: This is for compatibility with the Web application. A future refactoring might be able
  # to remove this dependency.
  class Coursemology::Polyglot::Language < ActiveRecord::Base; end
  # :nocov:
else
  class Coursemology::Polyglot::Language; end
end

# An abstract language. This is a class which can represent a family of languages. Languages become
# concrete and can be used when that language includes +Coursemology::Polyglot::ConcreteLanguage+.
#
# Languages define their own scripts and stylesheets needed to syntax highlight code.
#
# Each subclass represents a language ancestry, such as differing language versions (see the Python
# language definition.) Derived languages can be defined at runtime to utilise the syntax
# highlighting capabilities of the root language, while requiring a separate runtime environment to
# run programs written in the derived language.
#
# Do *NOT* remove languages after they have been defined because the types specified in this
# library can have references stored in code (e.g. in a database)
class Coursemology::Polyglot::Language
  extend ActiveSupport::Autoload

  eager_autoload do
    autoload :Python
    autoload :JavaScript
  end

  # Marks the current class as a concrete language.
  #
  # Concrete languages can be instantiated and used.
  #
  # @param [String] display_name The display name for the language
  # @param [String] docker_image The Docker image to use for the given language. This defaults to
  #   the string generated by +Coursemology::Polyglot::ConcreteLanguage.docker_image+.
  def self.concrete_language(display_name, docker_image: nil)
    include Coursemology::Polyglot::ConcreteLanguage
    extend Coursemology::Polyglot::ConcreteLanguage::ClassMethods

    concrete_class_methods = Module.new do
      define_method(:display_name) { display_name }
      define_method(:docker_image) { docker_image } if docker_image
    end

    extend concrete_class_methods
  end
  private_class_method :concrete_language

  # Determines the concrete language subclasses of this language.
  #
  # @return [Array<Class>]
  def self.concrete_languages
    descendants.select do |klass|
      klass.ancestors.include?(Coursemology::Polyglot::ConcreteLanguage)
    end
  end

  # The name of the lexer or mode to use with a given library. This is inherited by classes.
  #
  # @param [String|nil] default The default lexer/mode to use for each library.
  # @param [String] rouge The overridden lexer to use for Rouge. This is optional and will
  #   default to +default+
  # @param [String] ace The overridden mode to use for Ace. This is optional and will
  #   default to +default+
  # @raise [ArgumentError] When no default is specified and the Rouge lexer or Ace mode is not
  #   specified.
  def self.syntax_highlighter(default = nil, rouge: default, ace: default)
    fail ArgumentError unless rouge && ace

    syntax_highlighter_class_methods = Module.new do
      define_method(:rouge_lexer) { rouge }
      define_method(:ace_mode) { ace }
    end
    extend syntax_highlighter_class_methods

    syntax_highlighter_instance_methods = Module.new do
      delegate :rouge_lexer, :ace_mode, to: :class
    end
    include syntax_highlighter_instance_methods
  end
  private_class_method :syntax_highlighter

  # Gets the display name of the language.
  #
  # @abstract
  # @return [String]
  def self.display_name
    fail NotImplementedError
  end

  # The Rouge lexer to use with this language.
  #
  # @abstract
  # @return [String]
  def self.rouge_lexer
    fail NotImplementedError
  end

  # The Ace mode to use with this language.
  #
  # @abstract
  # @return [String]
  def self.ace_mode
    fail NotImplementedError
  end
end