#!/usr/bin/env ruby
# ======================================================================
# faust2sc - Generate language modules from Faust XML.
# Copyright (C) 2005-2008 Stefan Kersten
# ======================================================================
# 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 2 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, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
# USA
# ======================================================================

# TODO:
#  rexml is dog slow, maybe use libxml?

require 'getoptlong'
require 'rexml/document'

PROGRAM         = File.basename($0)
PROGRAM_VERSION = "1.1.0"

class Array
  def flatten1
    res = []
    self.each { |l|
      res += l
    }
    res
  end
end

module REXML
  class Element
    def to_i
      self.text.to_i
    end
    def to_f
      self.text.to_f
    end
  end
end

class String
  def encapitalize
    self[0..0].upcase + self[1..-1]
  end
  def decapitalize(greedy=false)
    unless greedy
      self[0..0].downcase + self[1..-1]
    else
      res = self.clone
      (0..res.size-1).each { |i|
        c = res[i]
        if 65 <= c && c <= 90
          res[i] = c + 32
        else
          break
        end
      }
      res
    end
  end
end

def print_error(str)
  $stderr.print("#{PROGRAM}[ERROR] #{str}")
end

def print_info(str)
  $stderr.print("#{PROGRAM}[INFO]  #{str}")
end

module Faust
  class Widget
    attr_reader :type, :id, :label, :init, :min, :max, :step
    def initialize(node)
      @type = node.attributes["type"]
      @id   = node.attributes["id"]
      if (node.elements["label"].text)
        @label = node.elements["label"].text
      else
        print_info("No label for widget ID #{id} - calling it widget#{id}\n")
        @label = "widget#{id}"
      end
      if (@label =~ /^[0-9]/)
        plabel = @label
        @label = @type + "_" + @label
        print_info("Widget label `#{plabel}' prefixed to become `#{@label}'\n")
      end
      dict = node.elements
      @init = dict["init"].to_f
      @min  = dict["min"].to_f
      @max  = dict["max"].to_f
      @step = dict["step"].to_f
      if (@type == "button")
        @max  = 1 # no metadata other than name for buttons
        @step = 1
      end
    end
  end

  class UI
    attr_reader :active_widgets, :passive_widgets
    def initialize(node)
      @active_widgets  = node.get_elements("//activewidgets/widget").collect  { |x| Widget.new(x) }
      @passive_widgets = node.get_elements("//passivewidgets/widget").collect { |x| Widget.new(x) }
    end
  end

  class Plugin
    attr_reader :path, :name, :author, :copyright, :license, :inputs, :outputs, :ui
    def initialize(path, node)
      @path = path
      %w(name author copyright license).each { |name|
        instance_variable_set("@#{name}", node.elements["/faust/#{name}"].text)
      }
      %w(inputs outputs).each { |name|
        instance_variable_set("@#{name}", node.elements["/faust/#{name}"].text.to_i)
      }
      @ui = UI.new(node.elements["/faust/ui"])
    end
    def total_inputs
      inputs + ui.active_widgets.size
    end
    def Plugin::from_file(path)
      self.new(path, REXML::Document.new(File.open(path) { |io| io.read }))
    end
  end

  class Generator
    attr_reader :plugins, :options
    def initialize(plugins, options)
      @plugins = plugins
      @options = options
    end
    def lang
      "unknown"
    end
    def generate(io)
      generate_header(io)
      generate_body(io)
      generate_footer(io)
    end
    def generate_header(io)
    end
    def generate_footer(io)
    end
    def generate_body(io)
      plugins.each_with_index { |plugin,i|
        if plugin
          begin
            print_info("Generating #{lang} code for #{plugin.name} ...\n")
            generate_plugin(io, plugin)
            if i < (plugins.size - 1)
              io.print("\n")
            end
          rescue
            print_error("#{$!}\n")
            $!.backtrace.each { |l| print_error(l + "\n") }
            print_error("Omitting #{plugin.path}\n")
          end
        end
      }
      io.print("\n")
    end
    def generate_plugin(io, plugin)
      raise "#{self.class}::generate_plugin() missing!"
    end
  end
  
  module Language
    IDENTIFIER_REGEXP = /^[a-z][a-zA-Z0-9_]*[a-zA-Z0-9]?$/
    def make_identifier(name)
      # gentle identifier massage
      # remove quotes
      name = name.sub(/^"([^"]*)"/, "\\1")
      # replace invalid chars with underscores
      name = name.downcase.gsub(/[^a-zA-Z0-9_]/, "_")
      # reduce multiple underscores to one
      name = name.gsub(/__+/, "_")
      # remove leading/terminating underscores
      name = name.sub(/(^_|_$)/, "")
      # move leading digits to the end
      name = name.sub(/^([0-9]+)_/, "") + ("_#{$1}" if $1).to_s
      # if digit(s) only, prepend alpha prefix
      if (name[0..0] =~ /^[0-9]/)
        pname = name
        name = "w" + name
        print_info("Widget label `#{pname}' prefixed to become `#{name}'\n")
      end
      unless name =~ IDENTIFIER_REGEXP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "iple undP
        raise "ipl