module Pione
  module Parser
    # ParserError is raised when the parser finds syntax error.
    class ParserError < Parslet::ParseFailed
      # Creates an error.
      # @param [String] str
      #   target string
      # @param [Array<String>] expected
      #   expected names
      # @param [Parslet::Source] source
      #   parser source
      def initialize(str, expected, source)
        @str = str
        @expected = expected
        @source = source
        super(str)
      end

      # @api private
      def message
        line, column = @source.line_and_column
        expected = @expected.join(", ")
        left = @source.consume(@source.chars_left)
        "%s(expected: %s, line: %s, column: %s):\n%s" % [
          @str, expected, line, column, left
        ]
      end
    end

    # @api private
    class SyntaxErrorAtom < Parslet::Atoms::Base
      def initialize(msg, expected_elements=[], ignore_error)
        @msg = msg
        @expected_elements = expected_elements
        @ignore_error = ignore_error
      end

      def try(source, context, _)
        raise ParserError.new(@msg, @expected_elements, source)
      end

      def to_s_inner(prec)
        "SYNTAX_ERROR"
      end
    end

    class IgnoreErrorAtom < Parslet::Atoms::Base
      def initialize(atom)
        @atom = atom
      end

      def try(source, context)
        begin
          @atom.apply(source, context)
        rescue ParserError
          context.err(self, source, "", [])
        end
      end

      def to_s_inner(prec)
        "IGNORE_ERROR"
      end
    end

    # SyntaxError provides notification methods for syntax error.
    module SyntaxError
      # Raises syntax error. This method returns a dummy atom and the parser
      # evaluates it as error.
      # @param [String] msg
      #   error message
      # @param [Array<String>] expected_elements
      #   expected name list
      # @return [SyntaxErrorAtom]
      #   dummy atom for parser
      def syntax_error(msg, *expected_elements)
        SyntaxErrorAtom.new(msg, expected_elements, $ignore_error)
      end

      def ignore_error(&b)
        res = yield
        return IgnoreErrorAtom.new(res)
      end
    end

    class IgnoreAtom < Parslet::Atoms::Base
      def initialize(atom)
        @atom = atom
      end

      def try(source, context, consume_all)
        success, _ = result = @atom.try(source, context, consume_all)
        return sucess ? succ(nil) : result
      end

      def to_s_inner(prec)
        "IGNORE"
      end
    end

    module Ignore
      def ignore
        IgnoreAtom.new(self)
      end
    end

    class ExceptionAtom < Parslet::Atoms::Base
      def initialize(atom, exception)
        @atom = atom
        @exception = exception
      end

      def try(source, context)
        success, value = result = @atom.apply(source, context)
        if success
          esuccess, _ = @exception.apply(source, context)
          p @exception
          p source
          p esuccess
          if esuccess
            return result
          end
        end
        return result
      end

      def to_s_inner(prec)
        "EXCEPTION"
      end
    end

    module Exception
      def except(exception)
        #ExceptionAtom.new(self, exception)
        ExceptionAtom.new(self, exception)
      end
    end
  end
end

class Parslet::Atoms::Base
  include Pione::Parser::Ignore
  include Pione::Parser::Exception
end