#!/usr/bin/ruby
# coding: utf-8 (äüöß€)   vim: ts=2 sw=2 fdm=marker
#
# XUBetrieb model2csv-parser main.rb
#
# (c) 2011 Matthias Lüttgert, ENDA GmbH & Co. KG
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, see <http://www.gnu.org/licenses/>.
#
# DEUTSCH:
#
# Dieses Programm ist freie Software. Sie können es unter den Bedingungen der GNU
# General Public License, wie von der Free Software Foundation veröffentlicht,
# weitergeben und/oder modifizieren, entweder gemäß Version 3 der Lizenz oder
# (nach Ihrer Option) jeder späteren Version.
#
# Die Veröffentlichung dieses Programms erfolgt in der Hoffnung, daß es Ihnen von
# Nutzen sein wird, aber OHNE IRGENDEINE GARANTIE, sogar ohne die implizite
# Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN
# ZWECK. Details finden Sie in der GNU General Public License.
#
# Sie sollten ein Exemplar der GNU General Public License zusammen mit diesem
# Programm erhalten haben. Falls nicht, siehe <http://www.gnu.org/licenses/>.
#
# == Kein Zweck ==
#
# Diese Programm stellt eine Ruby-GTK-Anwendung zur Verfügung, die eine MagicDraw
# Versionn 16.5 XML Modelldatei des XUBetrieb-Modells parsen und die enthaltenen
# Datenmodelle, Entitäten, Attribute, Attributbeschreibungen und Datentypen
# in einer CSV-Datei ablegen kann und
# die eine MagicDraw Version 16.5 Export -- MOF XMI File -- MOF Whole Model
# Modelldatei des XUBetrieb-Modells parsen und die enthaltenen
# Datenmodelle, Entitäten, Attribute, Attributbeschreibungen und Datentypen
# in einer CSV-Datei ablegen kann
# oder dies ganz oder teilweise nicht oder anders als erwartet kann bzw. tut.
#
# == Inbetriebnahme ==
#
# Sie benötigen Ruby Version 1.9.1, GTK2, libglade2, Ruby libxml und die
# Free ENDA Ruby Standard Library, um diese Anwendung ausführen zu können.
#
# Andere Versionen können ebenfalls funktionieren.
#
# Erwerben Sie tiefe Kenntnisse der Softwareentwicklung. Kaufen Sie sich ein
# Ruby-Buch, z. B. "Programming Ruby" von Thomas und lesen Sie dies.
# Zusammen mit den zunächst erworbenen tiefen Kenntnissen der
# Softwareentwicklung klappt es schon.


begin
  require 'rubygems'
  require 'libglade2'
  require '../../free_ruby_enda/lib/stnd'
  load "gui_callbacks.rb"
  require 'xml/libxml'
  require 'rbconfig'
  require 'yaml'
  require 'csv'

  include Config

  $unattended = false

  APPNAME = "XUBetrieb Datenstrukturparser I"
  APPVERSION = "0.1.1"
  ABOUT = <<EOS

(c) ENDA GmbH & Co. KG 2011

Besuchen Sie die Homepage der ENDA GmbH & Co. KG unter
http://enda.eu
für Informationen über die ENDA GmbH & Co. KG
EOS


  class Processor
    attr_accessor :infile_name, :outfile_name
    include Config
    def initialize (glade)
      @glade = glade
      @infile_name = nil
      @outfile_name = nil
    end
    def is_ready
      @infile_name && @outfile_name
    end
    def process(mode_num, mode_string)
      puts "Working in Mode #{mode_num} (#{mode_string})"
      puts "Zeit: " + now + " Start"
      converter = Converter.new(@glade, @infile_name, @outfile_name)
      converter.prepare
      if mode_string == "MOF Whole Model Export"
        converter.parse1
      elsif mode_string == "MagicDraw16 XML Project"
        converter.parse2
      else
        puts "Den Mode-String <#{mode_string}> kenne ich nicht."
      end
      converter.close
      puts "Zeit: " + now + " Ende"
    end
  end

  class Converter
    def initialize ( glade, infile_name, outfile_name )
      @infile_name = infile_name
      @outfile_name = outfile_name
      @glade = glade
      @file_num_lines = 0
      @bar = @glade['progressbar1']
      @bar.pulse_step = 0.05
      self.pulse
      @odoc = nil
      @oroot = nil
      @dbh = nil
    end

    def prepare
      @outfile = File.new(@outfile_name, "w")
      #GC.disable
      @doc = XML::Document.file(@infile_name)
      puts "Zeit: " + now + " XML \'Document object erzeugt\'"
      puts "Zeit: "+ now + " vor Validierung der Eingabe"
    end

    def parse1
      root = @doc.root
      puts "Root element: #{root.name}"

      @outfile.puts "B.pflicht;Entity;Attribut;Beschreibung;Typ"

      target2 = %q^/*[namespace-uri()='http://schema.omg.org/spec/XMI/2.1' and local-name()='XMI']/*[namespace-uri()='http://schema.omg.org/spec/MOF/2.0/emof.xml' and local-name()='Package']/nestedPackage/nestedPackage^
      target2x = %q^/xmi:XMI/*[namespace-uri()='http://schema.omg.org/spec/MOF/2.0/emof.xml' and local-name()='Package']/nestedPackage/nestedPackage^
      # bbis_exportdatum = root.find_first('/BBIS/EXPORTDATUM')
      root.find(target2).each do |berichtspflicht|
        # puts "Berichtspflicht <" + berichtspflicht.attributes["name"] + ">"
        
        berichtspflicht.find('nestedPackage').each do |nepa|
          if nepa.attributes["name"] == 'Datenmodell'
            # Got him
            nepa.find('ownedType').each do |oty|
              # puts "Berichtspflicht <" + berichtspflicht.attributes["name"] + ">    Entity <" + oty.attributes["name"] + ">"
              
              oty.find('ownedAttribute').each do |my_attribute|
                beschreibung = nil
                type = nil
                datatype = nil
                name = my_attribute.attributes["name"]
                if type = my_attribute.attributes["type"]
                  target3 = %Q^/xmi:XMI/*/nestedPackage/nestedPackage/ownedType[@xmi:id=\'#{type}\']^
                  if type_node = root.find_first(target3)
                    datatype = type_node.attributes["name"]
                  else
                    target3 = %Q^/xmi:XMI/*/nestedPackage/nestedPackage/nestedPackage/ownedType[@xmi:id=\'#{type}\']^
                    root.find(target3).each do |oty2|
                      if oty2.attributes["id"] == type
                        datatype = oty2.attributes["name"]
                        break
                      end
                    end
                  end
                end
                if comment_node = my_attribute.find_first('ownedComment')
                  beschreibung = comment_node.attributes["body"]
                end
                
                @outfile.puts berichtspflicht.attributes["name"] + ";" + oty.attributes["name"] + ";" + name.to_s + ";\"" + beschreibung.to_s + '";' + datatype.to_s
              end
              # return name, beschreibung

            end
          end
          self.pulse
        end
      end
      
      # puts "Berichtspflicht <" + berichtspflicht.value + ">"
      
      @outfile
      puts "Zeit: " + now + " XML \'Document object erzeugt\'"
      @bar.fraction = 0.0
    end

    def parse2
      root = @doc.root
      puts "Root element: #{root.name}"

      @outfile.puts "B.pflicht;Entity;Attribut;Beschreibung;Typ"

      target2 = %q^/*[namespace-uri()='http://schema.omg.org/spec/XMI/2.1' and local-name()='XMI']/*[namespace-uri()='http://schema.omg.org/spec/MOF/2.0/emof.xml' and local-name()='Package']/nestedPackage/nestedPackage^
      target2x = %q^/xmi:XMI/uml:Model/packagedElement[@name="2_Erhebung"]/packagedElement^
      # bbis_exportdatum = root.find_first('/BBIS/EXPORTDATUM')
      root.find(target2x).each do |berichtspflicht|
        puts "Berichtspflicht <" + berichtspflicht.attributes["name"] + ">"
        berichtspflicht_name = berichtspflicht.attributes["name"]

        target3 = %q^packagedElement[@name="Datenmodell"]/packagedElement[@xmi:type="uml:Class"]^
        berichtspflicht.find(target3).each do |nepa|
          puts "Entity <" + nepa.attributes["name"] + ">"
          entity_name = nepa.attributes["name"]

          nepa.find('ownedAttribute').each do |oatt|
            name = oatt.attributes["name"]
            next if !name || name.length == 0
            type = nil
            if type_attribute = oatt.find_first('@type')
              type_id = type_attribute.value
              target4 = %Q^/xmi:XMI/uml:Model/packagedElement/packagedElement/packagedElement[@xmi:id="#{type_id}"]^
              if type_node = root.find_first(target4)
                type = type_node["name"]
              end
              if !type
                # Typ in Generic_DDL_Profile.xml suchen: Später. Muss erst eingebunden werden.
                target4 = %Q^/xmi:XMI/uml:Model/packagedElement/packagedElement/packagedElement/packagedElement[@xmi:id="#{type_id}"]^
                if type_node = root.find_first(target4)
                  type = type_node["name"]
                end
              end
              if !type
                # Typ in "XOEV-Basisdatentypen-XOEV-Basisdatentypen-W3C Data Types" suchen
                target4 = %Q^/xmi:XMI/uml:Model/packagedElement/packagedElement/packagedElement/packagedElement/packagedElement[@xmi:id="#{type_id}"]^
                if type_node = root.find_first(target4)
                  type = type_node["name"]
                end
              end
            end

            if comment_node = oatt.find_first('ownedComment/@body')
              comment = comment_node.value
            else
              comment = ""
            end

            if oatt.attributes['aggregation'] && oatt.attributes['aggregation'] == "composite"
              type = "composite" if !type
            end

            if !type
              if berichtspflicht_name == "17_BImSchV"
                type = ""
              else
                puts 'Typ fehlt: ' + berichtspflicht_name + ';' + entity_name + ';' + name.to_s + ';"' + comment + '"'
                type = ""
              end
            end
            @outfile.puts berichtspflicht_name + ';' + entity_name + ';' + name.to_s + ';"' + comment + '";' + type
            next
          end
          self.pulse
        end
      end

      # puts "Berichtspflicht <" + berichtspflicht.value + ">"

      @outfile
      puts "Zeit: " + now + " XML \'Document object erzeugt\'"
      @bar.fraction = 0.0
    end


    def close
      if !$unattended
        # FXMessageBox.information($main, MBOX_OK, "Fertig", "XML-Konvertierung wurde durchgeführt.")
      end

      return
      puts "Zeit: "+ now + " vor Validierung des Ergebnisses"
      # The LibXML documentation is buggy. The correct method for schema validation is validate_schema:

      if !@odoc.validate_schema(outschema)
        puts "ACHTUNG! Die Datei <#{@outfile_name}> entspricht nicht dem Schema <#{@out_schema_name}>!"
        puts "Die Konvertierung ist fehlerhaft. Da muss RISA ran!"
        exit
      else
        puts "Erfolg: Die Datei <#{@outfile_name}> entspricht dem Schema <#{@out_schema_name}>!"
      end
    end

    def pulse
      @bar.pulse
      Gtk.main_iteration while Gtk.events_pending?
    end

    def test
      puts "Start am/um #{Stnd.now} (test)"
      self.pulse
      puts "Ende am/um #{Stnd.now}"
      @bar.fraction = 0.0
    end


  end
  
  def now
    return Time.now.strftime("%Y-%m-%d %H:%M:%S")
  end

  def test_stuff
    puts "Environment:"
    ENV.each { |key, value| puts "#{key} => #{value}" }
    puts "\nConfiguration:"
    include Config
    CONFIG.each { |key, value| puts "#{key} => #{value}" }
    puts "\nOperating System is: #{CONFIG['host_os']}"
  end

  class ConfigSettings
    attr_accessor :input_preselection
    attr_accessor :output_preselection
    def fill_missing
      @input_preselection = "." unless @input_preselection
      @output_preselection = "." unless @output_preselection
    end
    def ConfigSettings.write_config(me)
      open('converter.conf', 'w') { |f|
        YAML.dump(me, f)
      }
    end
    def ConfigSettings.read_config()
      # copes with non-existent or empty config file
      my_config = nil
      begin
        # Creates ConfigSettings from 'converter.conf' if the latter is valid
        open('converter.conf') { |f|
          my_config = YAML.load(f)
        }
        raise unless my_config
        return my_config
      rescue
        return ConfigSettings.new()
      end
    end
  end

  class Gui
    include Gui_callbacks
    include GetText
    attr :glade
    attr_accessor :processor

    def initialize(path_or_data, root = nil, domain = nil, localedir = nil, flag = GladeXML::FILE)
      bindtextdomain(domain, localedir, nil, "UTF-8")
      @glade = GladeXML.new(path_or_data, root, domain, localedir, flag) {|handler| method(handler)}
      @processor = nil
    end
  end


  # Program Start (main)
  # Load Configuration Settings, if any:
  $my_config = ConfigSettings.read_config()
  $my_config.fill_missing
  # Start GUI
  if ARGV[0] == "unattended"
    $unattended = 1
    processor = Processor.new()
    processor.infile_name  = ARGV[1]
    processor.outfile_name = ARGV[2]
    processor.process
    exit! true
  end

  # Alter Original-FOX-Call:
  # interface

  # Main program
  if __FILE__ == $0
    # Set values as your own application.
    PROG_PATH = "xubetrieb_parser.glade"
    gui = Gui.new(PROG_PATH, nil, APPNAME)
    print gui.glade.to_s, " Glade Filename: <", gui.glade.filename, ">\n"
    puts "Creating Processor"
    gui.processor = Processor.new(gui.glade)
    gui.processor.infile_name  = $my_config.input_preselection
    gui.processor.outfile_name = $my_config.output_preselection
    gui.glade['entry1'].set_text $my_config.input_preselection
    gui.glade['entry2'].set_text $my_config.output_preselection
    # gui.glade['combobox1'].set_popdown_strings ["MOF Whole Model Export", "MagicDraw16 XML Project"]
    gui.glade['combobox1'].append_text("MOF Whole Model Export")
    gui.glade['combobox1'].append_text("MagicDraw16 XML Project")
    gui.glade['combobox1'].active = 1   # "MagicDraw16 XML Project"
    Gtk.main
  end


  exit! true
rescue Exception => rumms
  puts "Unexpected Exc <#{rumms}>"
  print rumms.backtrace.join("\n")
end
