module Sol
  # This represents a class at the sol level. Sol is a syntax tree,
  # so here the only child (or children) is a body.
  # Body may either be a MethodStatement, or Statements (either empty or
  # containing MethodStatement)
  #
  # We store the class name and the parfait class
  #
  # The Parfait class gets created by to_parfait, ie only after that is the clazz
  # attribute set.
  #
  class ClassExpression < Expression
    attr_reader :name, :super_class_name , :body
    attr_reader :clazz

    def initialize( name , supe , body)
      @name = name
      @super_class_name = supe || :Object
      raise "what body #{body}" unless body.is_a?(Statements)
      @body = body
    end

    # This creates the Parfait class.
    # Creating the class involves creating the instance_type (or an initial version)
    # which means knowing all used names. So we go through the code looking for
    # InstanceVariables or InstanceVariable Assignments, to do that.
    def to_parfait
      @clazz = Parfait.object_space.get_class_by_name(@name )
      if(@clazz)
        if( @super_class_name != clazz.super_class_name)
          raise "Superclass mismatch for #{@name} , was #{clazz.super_class_name}, now: #{super_class_name}"
        end
      else
        @clazz = Parfait.object_space.create_class(@name , @super_class_name )
      end
      create_types
      @body.statements.each {|meth| meth.to_parfait(@clazz)}
      @clazz
    end

    # We transforms every method (class and object)
    # Other statements are not yet allowed (baring in mind that attribute
    # accessors are transformed to methods in the ruby layer )
    #
    # As there is no class equivalnet in code, a SlotCollection is returned,
    # which is just a list of SlotMachine::MethodCompilers
    # The compilers help to transform the code further, into Risc next
    def to_slot( _ )
      method_compilers =  body.statements.collect do |node|
        case node
        when MethodExpression
          node.to_slot(@clazz)
        when ClassMethodExpression
          node.to_slot(@clazz.single_class)
        else
          raise "Only methods for now #{node.class}:#{node}"
        end
      end
      SlotMachine::SlotCollection.new(method_compilers)
    end

    # goes through the code looking for instance variables and their assignments.
    # Adding each to the respective type, ie class or singleton_class, depending
    # on if they are instance or class instance variables.
    #
    # Class variables are deemed a design mistake, ie not implemented (yet)
    def create_types
      self.body.statements.each do |node|
        case node
        when MethodExpression
          target = @clazz
        when ClassMethodExpression
          target = @clazz.single_class
        else
          raise "Only methods for now #{node.class}:#{node}"
        end
        node.each do |exp|
          case exp
          when InstanceVariable, IvarAssignment
            target.add_instance_variable( exp.name , :Object  )
          when ClassVariable #, ClassVarAssignment
            raise "Class variables not implemented #{node.name}"
          end
        end
      end
    end
    def to_s(depth = 0)
      derive = super_class_name ? "< #{super_class_name}" : ""
      at_depth(depth , "class #{name} #{derive}\n#{@body.to_s(depth + 1)}\nend")
    end
  end
end