require 'rgen/ecore/ecore'

module RGen
  
module ECore

class ECoreToRuby
  
  def initialize
    @modules = {}
    @classifiers = {}
    @features_added = {}
    @in_create_module = false
  end

  def create_module(epackage)
    return @modules[epackage] if @modules[epackage]
    
    top = (@in_create_module == false)
    @in_create_module = true

    m = Module.new do
      extend RGen::MetamodelBuilder::ModuleExtension
    end
    @modules[epackage] = m

    epackage.eSubpackages.each{|p| create_module(p)}
    m._set_ecore_internal(epackage)

    create_module(epackage.eSuperPackage).const_set(epackage.name, m) if epackage.eSuperPackage

    # create classes only after all modules have been created
    # otherwise classes may be created multiple times
    if top
      epackage.eAllClassifiers.each do |c| 
        if c.is_a?(RGen::ECore::EClass)
          create_class(c)
        elsif c.is_a?(RGen::ECore::EEnum)
          create_enum(c)
        end
      end
      @in_create_module = false
    end
    m
  end

  def create_class(eclass)
    return @classifiers[eclass] if @classifiers[eclass]

    c = Class.new(super_class(eclass)) do
      abstract if eclass.abstract
      class << self
        attr_accessor :_ecore_to_ruby
      end
    end
    class << eclass
      attr_accessor :instanceClass
      def instanceClassName
        instanceClass.to_s
      end
    end
    eclass.instanceClass = c
    c::ClassModule.module_eval do
      alias _method_missing method_missing
      def method_missing(m, *args)
        if self.class._ecore_to_ruby.add_features(self.class.ecore)
          send(m, *args)
        else
          _method_missing(m, *args)
        end
      end
      alias _respond_to respond_to?
      def respond_to?(m, include_all=false)
        self.class._ecore_to_ruby.add_features(self.class.ecore)
        _respond_to(m)
      end
    end
    @classifiers[eclass] = c
    c._set_ecore_internal(eclass)
    c._ecore_to_ruby = self

    create_module(eclass.ePackage).const_set(eclass.name, c)
    c
  end

  def create_enum(eenum)
    return @classifiers[eenum] if @classifiers[eenum]

    e = RGen::MetamodelBuilder::DataTypes::Enum.new(eenum.eLiterals.collect{|l| l.name.to_sym})
    @classifiers[eenum] = e

    create_module(eenum.ePackage).const_set(eenum.name, e)
    e
  end

  class FeatureWrapper
    def initialize(efeature, classifiers)
      @efeature = efeature
      @classifiers = classifiers
    end
    def value(prop)
      return false if prop == :containment && @efeature.is_a?(RGen::ECore::EAttribute)
      @efeature.send(prop)
    end
    def many?
      @efeature.many
    end
    def reference?
      @efeature.is_a?(RGen::ECore::EReference)
    end
    def opposite
      @efeature.eOpposite
    end
    def impl_type
      etype = @efeature.eType
      if etype.is_a?(RGen::ECore::EClass) || etype.is_a?(RGen::ECore::EEnum)
        @classifiers[etype]
      else
        ic = etype.instanceClass
        if ic
          ic
        else
          raise "unknown type: #{etype.name}" 
        end
      end
    end
  end

  def add_features(eclass)
    return false if @features_added[eclass]
    c = @classifiers[eclass]
    eclass.eStructuralFeatures.each do |f|
      w1 = FeatureWrapper.new(f, @classifiers) 
      w2 = FeatureWrapper.new(f.eOpposite, @classifiers) if f.is_a?(RGen::ECore::EReference) && f.eOpposite
      c.module_eval do
        if w1.many?
          _build_many_methods(w1, w2)
        else
          _build_one_methods(w1, w2)
        end
      end
    end
    @features_added[eclass] = true
    eclass.eSuperTypes.each do |t|
      add_features(t)
    end
    true
  end

  def super_class(eclass)
    super_types = eclass.eSuperTypes
    case super_types.size
    when 0
      RGen::MetamodelBuilder::MMBase
    when 1
      create_class(super_types.first)
    else
      RGen::MetamodelBuilder::MMMultiple(*super_types.collect{|t| create_class(t)})
    end
  end

end

end

end

