pp caller
module Coupler
  module Models
    class Transformer < Sequel::Model
      include CommonModel

      plugin :serialization, :marshal, :allowed_types, :code

      TYPES = %w{string integer datetime}
      EXAMPLES = {
        'string'   => 'Test',
        'integer'  => 123,
        'datetime' => lambda { Time.now }
      }

      def accepts_type?(type)
        allowed_types.is_a?(Array) && allowed_types.include?(type)
      end

      def transform(data, options)
        input = data[options[:in]]
        runner = Runner.new(code, input)
        output = runner.run
        data[options[:out]] = output
        data
      end

      def preview
        return nil  if allowed_types.nil? || code.nil? || code == ""

        result = {'success' => true}
        examples = EXAMPLES.reject { |k, v| !allowed_types.include?(k) }
        examples.each_pair do |type, obj|
          obj = obj.call  if obj.is_a?(Proc)
          result[type] = { :in => obj }
          begin
            transform(result[type], {:in => :in, :out => :out})

            expected_type = result_type == 'same' ? type : result_type
            actual_type = case result[type][:out]
                          when String then "string"
                          when Fixnum then "integer"
                          when Time, Date, DateTime then "datetime"
                          when NilClass then "null"
                          end

            if actual_type != "null" && expected_type != actual_type
              raise TypeError, "expected #{expected_type}, got #{actual_type}"
            end

          rescue Exception => e
            result[type][:out] = e
            result['success'] = false
          end
        end
        result
      end

      def field_changes(*fields)
        fields.inject({}) do |result, field|
          result[field.id] = hash = {}
          if result_type != 'same'
            hash[:type] = result_type.to_sym

            # FIXME: don't use db_type anymore
            hash[:db_type] =
              case result_type
              when 'integer'  then 'int(11)'
              when 'string'   then 'varchar(255)'
              when 'datetime' then 'datetime'
              end
          end
          result
        end
      end

      private
        def validate
          super
          validates_presence [:name, :allowed_types, :result_type, :code]
          validates_unique :name
          validates_includes TYPES + ['same'], :result_type

          if errors.on(:allowed_types).nil?
            bad = (allowed_types - TYPES).uniq
            if !bad.empty?
              errors.add(:allowed_types, "has invalid type(s): #{bad.join(', ')}")
            end
          end

          if errors.on(:code).nil?
            io = java.io.ByteArrayInputStream.new(code.to_java_bytes)
            begin
              JRuby.runtime.parseInline(io, "line", nil)
            rescue Exception => e
              errors.add(:code, "has errors: #{e.to_s}")
            end
          end

          if errors.empty?
            result = preview
            if !(result && result['success'])
              errors.add(:code, "has errors")
            end
          end
        end
    end
  end
end

require File.join(File.dirname(__FILE__), "transformer", "runner")