$:.unshift File.join(File.dirname(__FILE__),"..","lib")
$:.unshift File.join(File.dirname(__FILE__),"..","test")

require 'test/unit'
require 'rgen/transformer'
require 'rgen/environment'
require 'rgen/util/model_comparator'
require 'metamodels/uml13_metamodel'
require 'testmodel/class_model_checker'

class TransformerTest < Test::Unit::TestCase

	class ModelIn
		attr_accessor :name
	end

	class ModelInSub < ModelIn
	end
	
	class ModelAIn
		attr_accessor :name
		attr_accessor :modelB
	end

	class ModelBIn
		attr_accessor :name
		attr_accessor :modelA
	end

	class ModelCIn
		attr_accessor :number
	end
	
	class ModelOut
		attr_accessor :name
	end
	
	class ModelAOut
		attr_accessor :name
		attr_accessor :modelB
	end
	
	class ModelBOut
		attr_accessor :name
		attr_accessor :modelA
	end
	
	class ModelCOut
		attr_accessor :number
	end
	
	class MyTransformer < RGen::Transformer
		attr_reader :modelInTrans_count
		attr_reader :modelAInTrans_count
		attr_reader :modelBInTrans_count
		
		transform ModelIn, :to => ModelOut do
			# aribitrary ruby code may be placed before the hash creating the output element
			@modelInTrans_count ||= 0; @modelInTrans_count += 1
			{ :name => name }
		end
		
		transform ModelAIn, :to => ModelAOut do
			@modelAInTrans_count ||= 0; @modelAInTrans_count += 1
			{ :name => name, :modelB => trans(modelB) }
		end
		
		transform ModelBIn, :to => ModelBOut do
			@modelBInTrans_count ||= 0; @modelBInTrans_count += 1
			{ :name => name, :modelA => trans(modelA) }
		end
		
		transform ModelCIn, :to => ModelCOut, :if => :largeNumber do
			# a method can be called anywhere in a transformer block
			{ :number => duplicateNumber }
		end

		transform ModelCIn, :to => ModelCOut, :if => :smallNumber do
			{ :number => number / 2 }
		end
		
		method :largeNumber do
			number > 1000
		end
		
		method :smallNumber do
			number < 500
		end
		
		method :duplicateNumber do
			number * 2;
		end
		
	end
	
	class MyTransformer2 < RGen::Transformer
		# check that subclasses are independent (i.e. do not share the rules)
		transform ModelIn, :to => ModelOut do
			{ :name => name }
		end
	end	
	
	def test_transformer
		from = ModelIn.new
		from.name = "TestName"
		env_out = RGen::Environment.new
		t = MyTransformer.new(:env_in, env_out)
		assert t.trans(from).is_a?(ModelOut)
		assert_equal "TestName", t.trans(from).name
		assert_equal 1, env_out.elements.size
		assert_equal env_out.elements.first, t.trans(from)
		assert_equal 1, t.modelInTrans_count
	end
  
  def test_transformer_chain
    from = ModelIn.new
    from.name = "Test1"
    from2 = ModelIn.new
    from2.name = "Test2"
    from3 = ModelIn.new
    from3.name = "Test3"
    env_out = RGen::Environment.new
    elementMap = {}
    t1 = MyTransformer.new(:env_in, env_out, elementMap)
		assert t1.trans(from).is_a?(ModelOut)
		assert_equal "Test1", t1.trans(from).name
		assert_equal 1, t1.modelInTrans_count
    # modifying the element map means that following calls of +trans+ will be affected
    assert_equal( {from => t1.trans(from)}, elementMap )
    elementMap.merge!({from2 => :dummy})
    assert_equal :dummy, t1.trans(from2)
    # second transformer based on the element map of the first
    t2 = MyTransformer.new(:env_in, env_out, elementMap)
    # second transformer returns same objects
    assert_equal t1.trans(from).object_id, t2.trans(from).object_id
    assert_equal :dummy, t2.trans(from2)
    # and no transformer rule is evaluated at this point
		assert_equal nil, t2.modelInTrans_count
    # now transform a new object in second transformer
		assert t2.trans(from3).is_a?(ModelOut)
		assert_equal "Test3", t2.trans(from3).name
		assert_equal 1, t2.modelInTrans_count
    # the first transformer returns the same object without evaluation of a transformer rule
    assert_equal t1.trans(from3).object_id, t2.trans(from3).object_id
		assert_equal 1, t1.modelInTrans_count
  end
	
	def test_transformer_subclass
		from = ModelInSub.new
		from.name = "TestName"
		t = MyTransformer.new
		assert t.trans(from).is_a?(ModelOut)
		assert_equal "TestName", t.trans(from).name
		assert_equal 1, t.modelInTrans_count
	end
	
	def test_transformer_array
		froms = [ModelIn.new, ModelIn.new]
		froms[0].name = "M1"
		froms[1].name = "M2"
		env_out = RGen::Environment.new
		t = MyTransformer.new(:env_in, env_out)
		assert t.trans(froms).is_a?(Array)
		assert t.trans(froms)[0].is_a?(ModelOut)
		assert_equal "M1", t.trans(froms)[0].name
		assert t.trans(froms)[1].is_a?(ModelOut)
		assert_equal "M2", t.trans(froms)[1].name
		assert_equal 2, env_out.elements.size
		assert (t.trans(froms)-env_out.elements).empty?
		assert_equal 2, t.modelInTrans_count
	end
	
	def test_transformer_cyclic
		# setup a cyclic dependency between fromA and fromB
		fromA = ModelAIn.new
		fromB = ModelBIn.new
		fromA.modelB = fromB
		fromA.name = "ModelA"
		fromB.modelA = fromA
		fromB.name = "ModelB"
		env_out = RGen::Environment.new
		t = MyTransformer.new(:env_in, env_out)
		# check that trans resolves the cycle correctly (no endless loop)
		# both elements, fromA and fromB will be transformed with the transformation
		# of the first element, either fromA or fromB
		assert t.trans(fromA).is_a?(ModelAOut)
		assert_equal "ModelA", t.trans(fromA).name
		assert t.trans(fromA).modelB.is_a?(ModelBOut)
		assert_equal "ModelB", t.trans(fromA).modelB.name
		assert_equal t.trans(fromA), t.trans(fromA).modelB.modelA
		assert_equal t.trans(fromB), t.trans(fromA).modelB
		assert_equal 2, env_out.elements.size
		assert (env_out.elements - [t.trans(fromA), t.trans(fromB)]).empty?
		assert_equal 1, t.modelAInTrans_count
		assert_equal 1, t.modelBInTrans_count
	end
	
	def test_transformer_conditional
		froms = [ModelCIn.new, ModelCIn.new, ModelCIn.new]
		froms[0].number = 100
		froms[1].number = 1000
		froms[2].number = 2000

		env_out = RGen::Environment.new
		t = MyTransformer.new(:env_in, env_out)

		assert t.trans(froms).is_a?(Array)
		assert_equal 2, t.trans(froms).size
		
		# this one matched the smallNumber rule
		assert t.trans(froms[0]).is_a?(ModelCOut)
		assert_equal 50, t.trans(froms[0]).number
		
		# this one did not match any rule
		assert t.trans(froms[1]).nil?

		# this one matched the largeNumber rule
		assert t.trans(froms[2]).is_a?(ModelCOut)
		assert_equal 4000, t.trans(froms[2]).number
		
		# elements in environment are the same as the ones returned
		assert_equal 2, env_out.elements.size
		assert (t.trans(froms)-env_out.elements).empty?
	end
	
	class CopyTransformer < RGen::Transformer
		include UML13
		def transform
			trans(:class => UML13::Package)
		end
		UML13.ecore.eClassifiers.each do |c|
		  copy c.instanceClass 
    end
	end

	MODEL_DIR = File.join(File.dirname(__FILE__),"testmodel")

	include Testmodel::ClassModelChecker
    include RGen::Util::ModelComparator
	
	def test_copyTransformer
		envIn = RGen::Environment.new
		envOut = RGen::Environment.new

    EASupport.instantiateUML13FromXMI11(envIn, MODEL_DIR+"/ea_testmodel.xml") 
		
		CopyTransformer.new(envIn, envOut).transform
		checkClassModel(envOut)
		assert modelEqual?(
		  envIn.find(:class => UML13::Model).first,
		  envOut.find(:class => UML13::Model).first)
	end
	
end
