require 'journey/nfa/dot'

module Journey
  module GTG
    class TransitionTable
      include Journey::NFA::Dot

      attr_reader :memos

      def initialize
        @regexp_states = Hash.new { |h,k| h[k] = {} }
        @string_states = Hash.new { |h,k| h[k] = {} }
        @accepting     = {}
        @memos         = Hash.new { |h,k| h[k] = [] }
      end

      def add_accepting state
        @accepting[state] = true
      end

      def accepting_states
        @accepting.keys
      end

      def accepting? state
        @accepting[state]
      end

      def add_memo idx, memo
        @memos[idx] << memo
      end

      def memo idx
        @memos[idx]
      end

      def eclosure t
        Array(t)
      end

      def move t, a
        move_string(t, a).concat move_regexp(t, a)
      end

      def to_json
        require 'json'

        simple_regexp = Hash.new { |h,k| h[k] = {} }

        @regexp_states.each do |from, hash|
          hash.each do |re, to|
            simple_regexp[from][re.source] = to
          end
        end

        JSON.dump({
          :regexp_states => simple_regexp,
          :string_states => @string_states,
          :accepting     => @accepting
        })
      end

      def to_svg
        svg = IO.popen("dot -Tsvg", 'w+') { |f|
          f.write to_dot
          f.close_write
          f.readlines
        }
        3.times { svg.shift }
        svg.join.sub(/width="[^"]*"/, '').sub(/height="[^"]*"/, '')
      end

      def visualizer paths, title = 'FSM'
        viz_dir   = File.join File.dirname(__FILE__), '..', 'visualizer'
        fsm_js    = File.read File.join(viz_dir, 'fsm.js')
        fsm_css   = File.read File.join(viz_dir, 'fsm.css')
        erb       = File.read File.join(viz_dir, 'index.html.erb')
        states    = "function tt() { return #{to_json}; }"

        fun_routes = paths.shuffle.first(3).map do |ast|
          ast.map { |n|
            case n
            when Nodes::Symbol
              case n.left
              when ':id' then rand(100).to_s
              when ':format' then %w{ xml json }.shuffle.first
              else
                'omg'
              end
            when Nodes::Terminal then n.symbol
            else
              nil
            end
          }.compact.join
        end

        stylesheets = [fsm_css]
        svg         = to_svg
        javascripts = [states, fsm_js]

        # Annoying hack for 1.9 warnings
        fun_routes  = fun_routes
        stylesheets = stylesheets
        svg         = svg
        javascripts = javascripts

        require 'erb'
        template = ERB.new erb
        template.result(binding)
      end

      def []= from, to, sym
        case sym
        when String
          @string_states[from][sym] = to
        when Regexp
          @regexp_states[from][sym] = to
        else
          raise ArgumentError, 'unknown symbol: %s' % sym.class
        end
      end

      def states
        ss = @string_states.keys + @string_states.values.map(&:values).flatten
        rs = @regexp_states.keys + @regexp_states.values.map(&:values).flatten
        (ss + rs).uniq
      end

      def transitions
        @string_states.map { |from, hash|
          hash.map { |s, to| [from, s, to] }
        }.flatten(1) + @regexp_states.map { |from, hash|
          hash.map { |s, to| [from, s, to] }
        }.flatten(1)
      end

      private
      def move_regexp t, a
        return [] if t.empty?

        t.map { |s|
          @regexp_states[s].map { |re,v| re === a ? v : nil }
        }.flatten.compact.uniq
      end

      def move_string t, a
        return [] if t.empty?

        t.map { |s| @string_states[s][a] }.compact
      end
    end
  end
end
