require 'fileutils'
require 'set'

module XamplGenerator

  class GraphMLOut

    def initialize(elements_map)
      @elements_map = elements_map
    end

    def devise_filename(filename)
      bn = File.basename(filename)
      ext = File.extname(bn)
      bn = bn[0..(bn.size - ext.size - 1)]
      fn = "#{bn}.graphml"
    end

    def generate_class_nodes(element, include_mixins)

      node = @element_to_node_map[element.nstag]

      class_name = "#{ element.package }::#{ element.class_name }"
      mixin_name = "#{ element.package }::#{ element.class_name }"

      if element.persisted then
        write_entity_node(node, class_name, element.kind)
      else
        write_internal_node(node, class_name, element.kind)
      end

      if include_mixins then
        mixin_node = @mixed_in[element.nstag]
        #puts "#{ element.nstag } => #{ node }, mixin: [#{ mixin_node }]"
        if mixin_node then
          write_mixin_node(mixin_node, class_name)
        end
      end
#      puts "NODE #{ node } #{ class_name }"
      #      puts "NODE #{ mixin_node } #{ mixin_name }"
    end

    def generate_edges(element, include_mixins)
      # for each child, generate an entity-ref or internal-ref edge
      # for each child, generate a mixin-ref edge

      return if ignore_package(element.package)

      element.child_element_child.each do | celement |
        next if ignore_package(celement.package)

        cnstag = "{{#{ celement.namespace }}}#{ celement.element_name }"

        referenced_element = @ns_to_element_map[ cnstag ]
        next unless referenced_element

        this_node = @element_to_node_map[ element.nstag ]

        other_node = @element_to_node_map[ cnstag ]
        other_mixin = @mixed_in[ cnstag ]

        if referenced_element.persisted then
          @current_edge += 1
          write_entity_ref_edge(@current_edge, this_node, other_node)
#          puts "ER EDGE #{ @current_edge }, #{ this_node } --> #{ other_node } :: #{ element.class_name } --> #{ referenced_element.class_name }"
        else
          @current_edge += 1
          write_internal_ref_edge(@current_edge, this_node, other_node)
#          puts "IR EDGE #{ @current_edge }, #{ this_node } --> #{ other_node } :: #{ element.class_name } --> #{ referenced_element.class_name }"
        end
        if include_mixins then
          @current_edge += 1
          write_mixin_ref_edge(@current_edge, this_node, other_mixin)
          #        puts "MI EDGE #{ @current_edge }, #{ this_node } --> #{ other_mixin } :: #{ element.class_name } --> #{ referenced_element.class_name }"
        end
      end
    end

    def ignore_package(package)
      return true if @excluded_packages.member?(package)
      return false if @included_packages.nil?
      return true unless @included_packages.member?(package)
      return false
    end

    def write_graph_ml(filename, excluded_packages=[ ], included_packages=nil, include_mixins=true)
      filename = devise_filename(filename)

      @excluded_packages = Set.new(excluded_packages)
      @included_packages = included_packages ? Set.new(included_packages) : nil

      @element_to_node_map = {}
      @ns_to_element_map = {}
      @element_to_child_element_map = {}
      @mixed_in = {}

      nodes = 0
      edges = 0
      mixins = 0

      @elements_map.each_value do |elements|
        elements.element_child.each do |element|
          next if ignore_package(element.package)
          nodes += 1

          @element_to_node_map[element.nstag] = nodes
          @ns_to_element_map[element.nstag] = element
          @element_to_child_element_map[element.nstag] = map = {}

          element.child_element_child.each do | celement |
            edges += 1
            cnstag = "{{#{ celement.namespace }}}#{ celement.element_name }"
            map[cnstag] = edges
            unless @mixed_in.include?(cnstag) then
              mixins += 1
              @mixed_in[cnstag] = mixins
            end
          end
        end
      end

      @mixed_in.each do | k, v |
        @mixed_in[k] = v + nodes
      end

      #      puts "#{File.basename(__FILE__)}:#{__LINE__} #{ @element_to_node_map.inspect }"

      @reference_edges = edges

      File.open(filename, "w") do | out |
        @out = out

        if include_mixins then
          write_graphml_start(nodes + mixins, 2 * edges)
        else
          write_graphml_start(nodes, edges)
        end
        @elements_map.each_value do |elements|
          elements.element_child.each do |element|
            generate_class_nodes(element, include_mixins)
          end
        end

        @current_edge = 0
        @elements_map.each_value do |elements|
          elements.element_child.each do |element|
            generate_edges(element, include_mixins)
          end
        end
        write_graphml_end
#        puts "#{File.basename(__FILE__)}:#{__LINE__} EDGES:: predicted: #{ 2 * edges }, actual: #{ @current_edge }"
      end
      return nil
    end

    def write_entity_node(node, class_name, kind)
      @out << <<EOS
        <node id="n#{ node }">
            <data key="d0">
                <y:UMLClassNode>
                    <y:Geometry height="102.0"
                                width="111.0"
                                x="-5.5"
                                y="174.0"/>
                    <y:Fill color="#99CCFF"
                            transparent="false"/>
                    <y:BorderStyle color="#000000"
                                   type="line"
                                   width="2.0"/>
                    <y:NodeLabel alignment="center"
                                 autoSizePolicy="content"
                                 fontFamily="Dialog"
                                 fontSize="13"
                                 fontStyle="bold"
                                 hasBackgroundColor="false"
                                 hasLineColor="false"
                                 height="19.310546875"
                                 modelName="internal"
                                 modelPosition="c"
                                 textColor="#000000"
                                 visible="true"
                                 width="49.6904296875"
                                 x="30.65478515625"
                                 y="26.1328125">#{ class_name }
                    </y:NodeLabel>
                    <y:UML clipContent="true"
                           constraint=""
                           omitDetails="false"
                           stereotype="#{ kind }"
                           use3DEffect="false">
                        <!--y:AttributeLabel>bar
bar2</y:AttributeLabel>
                        <y:MethodLabel>foo()</y:MethodLabel-->
                    </y:UML>
                </y:UMLClassNode>
            </data>
            <data key="d1">UMLClass</data>
        </node>
EOS
    end

    def write_internal_node(node, class_name, kind)
      @out << <<EOS
        <node id="n#{ node }">
            <data key="d0">
                <y:UMLClassNode>
                    <y:Geometry height="102.0"
                                width="91.0"
                                x="4.5"
                                y="-1.0"/>
                    <y:Fill color="#CCFFCC"
                            transparent="false"/>
                    <y:BorderStyle color="#000000"
                                   type="line"
                                   width="1.0"/>
                    <y:NodeLabel alignment="center"
                                 autoSizePolicy="content"
                                 fontFamily="Dialog"
                                 fontSize="13"
                                 fontStyle="bold"
                                 hasBackgroundColor="false"
                                 hasLineColor="false"
                                 height="19.310546875"
                                 modelName="internal"
                                 modelPosition="c"
                                 textColor="#000000"
                                 visible="true"
                                 width="39.83251953125"
                                 x="25.583740234375"
                                 y="26.1328125">#{ class_name }
                    </y:NodeLabel>
                    <y:UML clipContent="true"
                           constraint=""
                           omitDetails="false"
                           stereotype="#{ kind }"
                           use3DEffect="false">
                        <!--y:AttributeLabel>bar</y:AttributeLabel>
                        <y:MethodLabel>foo()</y:MethodLabel-->
                    </y:UML>
                </y:UMLClassNode>
            </data>
            <data key="d1">UMLClass</data>
        </node>
EOS
    end

    def write_mixin_node(node, class_name)
      @out << <<EOS
        <node id="n#{ node }">
            <data key="d0">
                <y:UMLClassNode>
                    <y:Geometry height="102.0"
                                width="136.0"
                                x="-18.0"
                                y="349.0"/>
                    <y:Fill color="#FFCC99"
                            transparent="false"/>
                    <y:BorderStyle color="#000000"
                                   type="line"
                                   width="1.0"/>
                    <y:NodeLabel alignment="center"
                                 autoSizePolicy="content"
                                 fontFamily="Dialog"
                                 fontSize="13"
                                 fontStyle="bold"
                                 hasBackgroundColor="false"
                                 hasLineColor="false"
                                 height="19.310546875"
                                 modelName="internal"
                                 modelPosition="c"
                                 textColor="#000000"
                                 visible="true"
                                 width="110.818359375"
                                 x="12.5908203125"
                                 y="26.1328125">#{ class_name }
                    </y:NodeLabel>
                    <y:UML clipContent="true"
                           constraint=""
                           omitDetails="false"
                           stereotype="mixin"
                           use3DEffect="false">
                        <!--y:AttributeLabel></y:AttributeLabel>
                        <y:MethodLabel></y:MethodLabel-->
                    </y:UML>
                </y:UMLClassNode>
            </data>
            <data key="d1">UMLClass</data>
        </node>
EOS
    end

    def write_entity_ref_edge(edge, class_node, external_node)
      @out << <<EOS
        <edge id="e#{ edge }"
              source="n#{ class_node }"
              target="n#{ external_node }">
            <data key="d2">
                <y:PolyLineEdge>
                    <y:Path sx="0.0"
                            sy="0.0"
                            tx="0.0"
                            ty="0.0"/>
                    <y:LineStyle color="#000000"
                                 type="line"
                                 width="2.0"/>
                    <y:Arrows source="none"
                              target="short"/>
                    <y:EdgeLabel alignment="center"
                                 distance="2.0"
                                 fontFamily="Dialog"
                                 fontSize="12"
                                 fontStyle="plain"
                                 hasBackgroundColor="false"
                                 hasLineColor="false"
                                 height="4.0"
                                 modelName="six_pos"
                                 modelPosition="tail"
                                 preferredPlacement="anywhere"
                                 ratio="0.5"
                                 textColor="#000000"
                                 visible="true"
                                 width="4.0"
                                 x="30.000732421875"
                                 y="2.0"></y:EdgeLabel>
                    <y:BendStyle smoothed="false"/>
                </y:PolyLineEdge>
            </data>
            <data key="d3">UMLuses</data>
        </edge>
EOS
    end

    def write_internal_ref_edge(edge, class_node, internal_node)
      @out << <<EOS
        <edge id="e#{ edge }"
              source="n#{ class_node }"
              target="n#{ internal_node }">
            <data key="d2">
                <y:PolyLineEdge>
                    <y:Path sx="0.0"
                            sy="0.0"
                            tx="0.0"
                            ty="0.0"/>
                    <y:LineStyle color="#000000"
                                 type="line"
                                 width="1.0"/>
                    <y:Arrows source="none"
                              target="short"/>
                    <y:EdgeLabel alignment="center"
                                 distance="2.0"
                                 fontFamily="Dialog"
                                 fontSize="12"
                                 fontStyle="plain"
                                 hasBackgroundColor="false"
                                 hasLineColor="false"
                                 height="4.0"
                                 modelName="six_pos"
                                 modelPosition="tail"
                                 preferredPlacement="anywhere"
                                 ratio="0.5"
                                 textColor="#000000"
                                 visible="true"
                                 width="4.0"
                                 x="2.0"
                                 y="-38.529541015625"></y:EdgeLabel>
                    <y:BendStyle smoothed="false"/>
                </y:PolyLineEdge>
            </data>
            <data key="d3">UMLuses</data>
        </edge>
EOS
    end

    def write_mixin_ref_edge(edge, class_node, mixin_node)
      @out << <<EOS
        <edge id="e#{ edge }"
              source="n#{ class_node }"
              target="n#{ mixin_node }">
            <data key="d2">
                <y:PolyLineEdge>
                    <y:Path sx="0.0"
                            sy="0.0"
                            tx="0.0"
                            ty="0.0"/>
                    <y:LineStyle color="#000000"
                                 type="line"
                                 width="1.0"/>
                    <y:Arrows source="none"
                              target="white_delta"/>
                    <y:EdgeLabel alignment="center"
                                 distance="2.0"
                                 fontFamily="Dialog"
                                 fontSize="12"
                                 fontStyle="plain"
                                 hasBackgroundColor="false"
                                 hasLineColor="false"
                                 height="4.0"
                                 modelName="six_pos"
                                 modelPosition="tail"
                                 preferredPlacement="anywhere"
                                 ratio="0.5"
                                 textColor="#000000"
                                 visible="true"
                                 width="4.0"
                                 x="2.0"
                                 y="34.529541015625"></y:EdgeLabel>
                    <y:BendStyle smoothed="false"/>
                </y:PolyLineEdge>
            </data>
            <data key="d3">UMLinherits</data>
        </edge>
EOS
    end

    def write_graphml_start(nodes, edges)
      @out << <<EOS
<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns/graphml"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:y="http://www.yworks.com/xml/graphml"
         xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd">
    <key for="node"
         id="d0"
         yfiles.type="nodegraphics"/>
    <key attr.name="description"
         attr.type="string"
         for="node"
         id="d1"/>
    <key for="edge"
         id="d2"
         yfiles.type="edgegraphics"/>
    <key attr.name="description"
         attr.type="string"
         for="edge"
         id="d3"/>
    <key for="graphml"
         id="d4"
         yfiles.type="resources"/>
    <graph edgedefault="directed"
           id="G"
           parse.edges="#{ edges }"
           parse.nodes="#{ nodes }"
           parse.order="free">
EOS
    end

    def write_graphml_end
      @out << <<EOS
    </graph>
    <data key="d4">
        <y:Resources/>
    </data>
</graphml>
EOS
    end

  end
end