/**
    The abstract data type `ParseError` represents parse errors. It
    provides the source position (`SourcePos`) of the error
    and a list of error messages (`Message`). A `ParseError`
    can be returned by the function `parse`.
*/
public struct ParseError: CustomStringConvertible {
  var pos: SourcePos
  var messages: [Message]

  init(_ pos: SourcePos, _ messages: [Message]) {
    self.pos = pos
    self.messages = messages
  }

  init(_ pos: SourcePos) {
    self.pos = pos
    self.messages = []
  }

  init(_ pos: SourcePos, _ message: Message) {
    self.pos = pos
    self.messages = [message]
  }

  var sortedMessages: [Message] {
    return messages.sorted()
  }

  var isUnknown: Bool {
    return messages.isEmpty
  }

  mutating func addMessage (_ message: Message) {
    messages.append(message)
  }

  mutating func setPos (_ pos: SourcePos) {
    self.pos = pos
  }

  mutating func setMessage (_ message: Message) {
    messages = messages.filter({ $0 != message })
    messages.append(message)
  }

  public var description: String {
    return String(describing: pos) + ":\n" + showErrorMessage(sortedMessages)
  }
}

func unknownError<c: Collection, u> (_ state: State<c, u>) -> ParseError {
  return ParseError(state.pos)
}

func sysUnExpectError<a, c: Collection, u> (_ msg: String, _ pos: SourcePos) -> Reply<a, c, u> {
  return .error(Lazy{ ParseError(pos, .sysUnExpect(msg)) })
}

/**
    This abstract data type represents parse error messages. There are
    four kinds of messages. The fine distinction between different kinds
    of parse errors allows the system to generate quite good error messages
    for the user. Each kind of message is generated by different combinators:

    * A `sysUnExpect` message is automatically generated by the `satisfy`
      combinator. The argument is the unexpected input.

    * A `unExpect` message is generated by the `unexpected` combinator. The
      argument describes the unexpected item.

    * A `expect` message is generated by the `<?>` combinator. The argument
      describes the expected item.

    * A `Message` message is generated by the `fail` combinator. The argument
      is some general parser message.
*/
enum Message: Comparable {
  case sysUnExpect(String) // library generated unexpect
  case unExpect(String) // unexpected something
  case expect(String) // expecting something
  case message(String) // raw message

  // Extract the message string from an error message
  var string: String {
    switch self {
    case let .sysUnExpect(s): return String(reflecting: s)
    case let .unExpect(s): return String(reflecting: s)
    case let .expect(s): return String(reflecting: s)
    case let .message(s): return String(reflecting: s)
    }
  }

  var order: Int {
    switch self {
    case .sysUnExpect: return 0
    case .unExpect: return 1
    case .expect: return 2
    case .message: return 3
    }
  }
}

/**
    Return `true` only when orders are equal.
*/
func == (lhs: Message, rhs: Message) -> Bool {
  return lhs.order == rhs.order
}

/**
    Compares two error messages without looking at their content. Only
    the constructors are compared where:

    `sysUnExpect` < `unExpect` < `expect` < `message`
*/
func < (lhs: Message, rhs: Message) -> Bool {
  return lhs.order < rhs.order
}

func mergeError (_ e1: ParseError, _ e2: ParseError) -> ParseError {
  // prefer meaningful errors
  switch (e1.messages.isEmpty, e2.messages.isEmpty) {
  case (false, true): return e1
  case (true, false): return e2
  default:
    // select longest match
    if e1.pos > e2.pos {
      return e1
    } else if e1.pos < e2.pos {
      return e2
    } else {
      var messages = e1.messages
      messages.append(contentsOf: e2.messages)
      return ParseError(e1.pos, messages)
    }
  }
}

/**
    The standard function for showing error messages. Formats a list of
    error messages. The resulting string will be formatted like:

        unexpected {The first unExpect or a sysUnExpect message}
        expecting {comma separated list of expect messages}
        {comma separated list of Message messages}
*/
func showErrorMessage (_ msgs: [Message]) -> String {
  if msgs.isEmpty {
    return "unknown parse error"
  } else {
    let sysUnExpect = msgs.filter({ $0 == .sysUnExpect("") })
    let unExpect = msgs.filter({ $0 == .unExpect("") })
    let expect = msgs.filter({ $0 == .expect("") })
    let messages = msgs.filter({ $0 == .message("") })

    let showMessages = showMany("", messages)
    let showExpect = showMany("expecting", expect)
    let showUnExpect = showMany("unexpected", unExpect)
    var showSysUnExpect = ""
    if !unExpect.isEmpty || sysUnExpect.isEmpty {
    } else {
      let firstMsg = sysUnExpect.first?.string
      if let firstMsg = firstMsg, !firstMsg.isEmpty {
        showSysUnExpect = "unexpected \(firstMsg)"
      } else {
        showSysUnExpect = "unexpected end of input"
      }
    }

    return [showSysUnExpect, showUnExpect, showExpect, showMessages]
      .filter { !$0.isEmpty }.joined(separator: "\n")
  }
}

func showMany (_ pre: String, _ msgs: [Message]) -> String {
  let messages = msgs.map{ $0.string }.filter{ !$0.isEmpty }
  if messages.isEmpty {
    return ""
  } else if pre.isEmpty {
    return commasOr(messages)
  } else {
    return pre + " " + commasOr(messages)
  }
}

func commasOr (_ msgs: [String]) -> String {
  if msgs.isEmpty {
    return ""
  } else {
    var initial = msgs
    let last = initial.removeLast()
    if initial.isEmpty {
      return last
    } else {
      return initial.joined(separator: ", ") + " or " + last
    }
  }
}
