module Nginxtra
  class ConfigConverter
    def initialize(output)
      @converted = false
      @output = output
      @indentation = Nginxtra::Config::Indentation.new
    end

    def convert(options)
      raise Nginxtra::Error::ConvertFailed.new("The convert method can only be called once!") if converted?
      header
      compile_options options[:binary_status]
      config_file options[:config]
      footer
      converted!
    end

    private
    def header
      @output.puts "nginxtra.config do"
      @indentation + 1
    end

    def compile_options(status)
      return unless status
      options = (status[/^configure arguments:\s*(.*)$/, 1] || "").strip
      return if options.empty?
      options = options.split /\s+/
      process_passenger_compile_options! options

      options.each do |option|
        next if invalid_compile_option? option
        @output.print @indentation
        @output.puts %{compile_option "#{option}"}
      end
    end

    def process_passenger_compile_options!(options)
      return if options.select { |x| x =~ /^--add-module.*\/passenger.*/ }.empty?
      @output.print @indentation
      @output.puts "require_passenger!"

      options.delete_if do |x|
        next true if x =~ /^--add-module.*\/passenger.*/
        ["--with-http_ssl_module", "--with-http_gzip_static_module", "--with-cc-opt=-Wno-error"].include? x
      end
    end

    def invalid_compile_option?(option)
      return true if option =~ /--prefix=/
      return true if option =~ /--sbin-path=/
      return true if option =~ /--conf-path=/
      return true if option =~ /--pid-path=/
      false
    end

    def config_file(input)
      return unless input
      @output.print @indentation
      @output.puts %{file "nginx.conf" do}
      @indentation + 1
      line = Nginxtra::ConfigConverter::Line.new @indentation, @output

      each_token(input) do |token|
        line << token

        if line.terminated?
          line.puts
          line = Nginxtra::ConfigConverter::Line.new @indentation, @output
        end
      end

      raise Nginxtra::Error::ConvertFailed.new("Unexpected end of file!") unless line.empty?
      @indentation - 1
      @output.print @indentation
      @output.puts "end"
    end

    def each_token(input)
      token = Nginxtra::ConfigConverter::Token.new

      while c = input.read(1)
        if c == "#"
          chomp_comment input
        else
          token << c
        end

        yield token.instance while token.ready?
      end

      yield token.instance while token.ready?
      raise Nginxtra::Error::ConvertFailed.new("Unexpected end of file in mid token!") unless token.value.empty?
    end

    def chomp_comment(input)
      while c = input.read(1)
        break if c == "\n"
      end
    end

    def footer
      @indentation - 1
      @output.puts "end"
      raise Nginxtra::Error::ConvertFailed.new("Missing end blocks!") unless @indentation.done?
    end

    def converted!
      @converted = true
    end

    def converted?
      @converted
    end

    class Token
      TERMINAL_CHARACTERS = ["{", "}", ";"].freeze
      attr_reader :value

      def initialize(value = nil)
        @instance = true if value
        @value = value || ""
        @ready = false
      end

      def terminal_character?
        TERMINAL_CHARACTERS.include? @value
      end

      def end?
        @value == ";"
      end

      def block_start?
        @value == "{"
      end

      def block_end?
        @value == "}"
      end

      def instance
        raise Nginxtra::Error::ConvertFailed.new("Whoops!") unless ready?
        token = Nginxtra::ConfigConverter::Token.new @value
        reset!
        token
      end

      def <<(c)
        return space! if c =~ /\s/
        return terminal_character!(c) if TERMINAL_CHARACTERS.include? c
        @value << c
      end

      def ready?
        @instance || @ready || terminal_character?
      end

      def to_s
        if @value =~ /^\d+$/
          @value
        else
          %{"#{@value}"}
        end
      end

      private
      def space!
        return if @value.empty?
        @ready = true
      end

      def terminal_character!(c)
        if @value.empty?
          @value = c
        else
          @next = c
        end

        @ready = true
      end

      def reset!
        if @next
          @value = @next
        else
          @value = ""
        end

        @next = nil
        @ready = false
      end
    end

    class Line
      def initialize(indentation, output)
        @indentation = indentation
        @output = output
        @tokens = []
      end

      def <<(token)
        @tokens << token
      end

      def empty?
        @tokens.empty?
      end

      def terminated?
        @tokens.last.terminal_character?
      end

      def puts
        if @tokens.last.end?
          puts_line
        elsif @tokens.last.block_start?
          puts_block_start
        elsif @tokens.last.block_end?
          puts_block_end
        else
          raise Nginxtra::Error::ConvertFailed.new "Can't puts invalid line!"
        end
      end

      private
      def passenger?
        ["passenger_root", "passenger_ruby", "passenger_enabled"].include? @tokens.first.value
      end

      def puts_line
        raise Nginxtra::Error::ConvertFailed.new("Line must have a first label!") unless @tokens.length > 1
        return puts_passenger if passenger?
        print_indentation
        print_first
        print_args
        print_newline
      end

      def puts_passenger
        print_indentation

        if @tokens.first.value == "passenger_root"
          print_newline "passenger_root!"
        elsif @tokens.first.value == "passenger_ruby"
          print_newline "passenger_ruby!"
        elsif @tokens.first.value == "passenger_enabled"
          print_newline "passenger_on!"
        else
          raise Nginxtra::Error::ConvertFailed.new("Whoops!")
        end
      end

      def puts_block_start
        raise Nginxtra::Error::ConvertFailed.new("Block start must have a first label!") unless @tokens.length > 1
        print_indentation
        print_first
        print_args
        print_newline(" do")
        indent
      end

      def puts_block_end
        raise Nginxtra::Error::ConvertFailed.new("Block end can't have labels!") unless @tokens.length == 1
        unindent
        print_indentation
        print_newline("end")
      end

      def print_indentation
        @output.print @indentation.to_s
      end

      def print_first
        @output.print @tokens.first.value
      end

      def print_args
        args = @tokens[1..-2]
        return if args.empty?
        @output.print " "
        @output.print args.map(&:to_s).join(", ")
      end

      def print_newline(value = "")
        @output.puts value
      end

      def indent
        @indentation + 1
      end

      def unindent
        @indentation - 1
      end
    end
  end
end