/// This file defines the data types we use for words, rules, and
/// monoid presentations. Also, a few other fundamental algorithms
/// for working with permutations and reduction orders.

typealias Symbol = UInt8

let symbols: [Character] = ["a", "b", "c"]

func printSymbol(_ s: Symbol) -> Character {
  return symbols[Int(s)]
}

func parseSymbol(_ c: Character) -> Symbol {
  return Symbol(symbols.firstIndex(of: c)!)
}

typealias Word = [Symbol]

func printWord(_ word: Word) -> String {
  if word.isEmpty { return "1" }
  return String(word.map { printSymbol($0) })
}

func parseWord(_ str: String) -> Word {
  if str == "" || str == "1" { return [] }
  return str.map { parseSymbol($0) }
}

struct Rule: Hashable {
  var lhs: Word
  var rhs: Word
}

extension Rule: ExpressibleByStringLiteral, CustomStringConvertible {
  init(_ str: String) {
    let pair = str.split(separator: "=")
    precondition(pair.count == 2)
    self.lhs = parseWord(String(pair[0]))
    self.rhs = parseWord(String(pair[1]))
  }

  init(stringLiteral: String) {
    self.init(stringLiteral)
  }

  var description: String {
    return "\(printWord(lhs))=\(printWord(rhs))"
  }
}

struct Presentation: Hashable {
  var alphabet: Int
  var rules: [Rule]

  var longestRule: Int {
    var result = 0
    for rule in rules {
      result = max(result, rule.lhs.count)
      result = max(result, rule.rhs.count)
    }
    return result
  }
}

func printRules(_ rules: [Rule]) -> String {
  return rules.map { $0.description }.joined(separator: ",")
}

extension Presentation: ExpressibleByStringLiteral, CustomStringConvertible {
  init(_ str: String) {
    self.alphabet = 2
    self.rules = str.split(separator: ",").map { Rule(String($0)) }
  }

  init(stringLiteral: String) {
    self.init(stringLiteral)
  }

  var description: String {
    return printRules(rules)
  }
}

typealias Permutation = [Int]

func identityPermutation(_ n: Int) -> Permutation {
  return Permutation(0 ..< n)
}

func inversePermutation(_ perm: Permutation) -> Permutation {
  var result = Permutation(repeating: 0, count: perm.count)
  for (i, j) in perm.enumerated() {
    result[j] = i
  }
  return result
}

// TAOCP 4A 7.2.1.2 Algorithm L
func nextPermutation(_ perm: inout Permutation) -> Bool {
  var j = perm.count - 2
  while j >= 0 && perm[j] >= perm[j + 1] {
    j -= 1
  }
  if j < 0 { return true }

  var l = perm.count - 1
  while perm[j] >= perm[l] { l -= 1 }
  perm.swapAt(l, j)

  l = perm.count - 1
  var k = j + 1
  while k < l {
    perm.swapAt(k, l)
    k += 1
    l -= 1
  }

  return false
}

func allPermutations(_ alphabet: Int) -> [Permutation] {
  var perm = identityPermutation(alphabet)
  var perms: [Permutation] = []
  repeat {
    perms.append(perm)
  } while !nextPermutation(&perm)
  return perms
}

extension Word {
  func permuted(_ perm: Permutation) -> Word {
    return map { Symbol(perm[Int($0)]) }
  }
}

extension Rule {
  func permuted(_ perm: Permutation) -> Rule {
    return Rule(lhs: lhs.permuted(perm), rhs: rhs.permuted(perm))
  }
}

extension Presentation {
  func permuted(_ perm: Permutation) -> Presentation {
    return Presentation(alphabet: alphabet,
                        rules: rules.map { $0.permuted(perm) })
  }
}

enum CompareResult {
  case lessThan
  case equal
  case greaterThan
}

enum Order: Hashable {
  case shortlex
  case permutation(Permutation)
  case wreath([Int], Permutation)

  var simplified: Order {
    switch self {
    case .shortlex:
      return self
    case .permutation(let perm):
      // shortlex with identity permutation avoids some indirection
      if perm == identityPermutation(perm.count) {
        return .shortlex
      }
      return self
    case .wreath(_, _):
      return self
    }
  }

  func removeGenerator(_ a: Symbol) -> Order {
    func updatePermutation(_ perm: Permutation, removing i: Int) -> Permutation {
      return Permutation(perm[0 ..< i] + perm[(i + 1)...]).map {
        return $0 > perm[i] ? $0 - 1 : $0
      }
    }

    switch self {
    case .shortlex:
      return self

    case .permutation(let perm):
      return .permutation(updatePermutation(perm, removing: Int(a)))

    case .wreath(let degrees, let perm):
      var newDegrees = Array(degrees[0 ..< Int(a)] + degrees[(Int(a) + 1)...])
      let oldDegree = degrees[Int(a)]
      if newDegrees.firstIndex(of: oldDegree) == nil {
        newDegrees = newDegrees.map { $0 > oldDegree ? $0 - 1 : $0 }
      }
      let newPerm = updatePermutation(perm, removing: Int(a))
      if newDegrees.max()! == 0 { return .permutation(newPerm) }
      return .wreath(newDegrees, newPerm)
    }
  }
}

func shortlex(_ lhs: Word, _ lhsFrom: Int, _ lhsTo: Int,
              _ rhs: Word, _ rhsFrom: Int, _ rhsTo: Int,
              perm: Permutation) -> CompareResult {
  let lhsCount = (lhsTo - lhsFrom)
  let rhsCount = (rhsTo - rhsFrom)
  if lhsCount != rhsCount {
    return lhsCount < rhsCount ? .lessThan : .greaterThan
  }

  for i in 0 ..< lhsCount {
    let x = lhs[lhsFrom + i]
    let y = rhs[rhsFrom + i]
    if x != y {
      return perm[Int(x)] < perm[Int(y)] ? .lessThan : .greaterThan
    }
  }

  return .equal
}

// The "wreath product" or "recursive path" order:
//
// Sims, C. C. (1994). Computation with Finitely Presented Groups.
// Cambridge: Cambridge University Press.
//
func wreath(_ lhs: Word, _ lhsFrom: Int, _ lhsTo: Int,
            _ rhs: Word, _ rhsFrom: Int, _ rhsTo: Int,
            degrees: [Int], perm: Permutation) -> CompareResult {
  var i = lhsFrom
  var j = rhsFrom

  while true {
    if i == lhsTo {
      if j == rhsTo { return .equal }
      return .lessThan
    } else if j == rhsTo {
      return .greaterThan
    }

    if lhs[i] != rhs[j] { break }
    i += 1
    j += 1
  }

  func maxDegree(_ word: Word, _ from: Int, _ to: Int)
      -> (degree: Int, count: Int, symbol: Symbol?) {
    var degree = -1, count = 0
    var symbol: Symbol? = nil

    for s in word[from ..< to] {
      if degrees[Int(s)] > degree {
        degree = degrees[Int(s)]
        count = 1
        symbol = s
      } else if degrees[Int(s)] == degree {
        count += 1
        if symbol != s { symbol = nil }
      }
    }

    return (degree, count, symbol)
  }

  let (lhsMaxDegree, lhsCount, lhsHeadSymbol) = maxDegree(lhs, i, lhsTo)
  let (rhsMaxDegree, rhsCount, rhsHeadSymbol) = maxDegree(rhs, j, rhsTo)
  if lhsMaxDegree < rhsMaxDegree {
    return .lessThan
  } else if lhsMaxDegree > rhsMaxDegree {
    return .greaterThan
  } else if lhsCount < rhsCount {
    return .lessThan
  } else if lhsCount > rhsCount {
    return .greaterThan
  }

  if lhsHeadSymbol != nil && rhsHeadSymbol != nil {
    if lhsHeadSymbol != rhsHeadSymbol {
      return perm[Int(lhsHeadSymbol!)] < perm[Int(rhsHeadSymbol!)]
          ? .lessThan : .greaterThan
    }
  } else {
    if lhsMaxDegree == 0 {
      return shortlex(lhs, i, lhsTo, rhs, j, rhsTo, perm: perm)
    } else {
      let lhsHeadWord = lhs[i ..< lhsTo].filter { degrees[Int($0)] == lhsMaxDegree }
      let rhsHeadWord = rhs[j ..< rhsTo].filter { degrees[Int($0)] == rhsMaxDegree }

      let result = shortlex(lhsHeadWord, 0, lhsHeadWord.count,
                            rhsHeadWord, 0, rhsHeadWord.count,
                            perm: perm)
      if result != .equal { return result }
    }
  }

  if lhsMaxDegree == 0 { return .equal }

  var ii = i, jj = j
  while i < lhsTo {
    while i < lhsTo && degrees[Int(lhs[i])] != lhsMaxDegree { i += 1 }
    while j < rhsTo && degrees[Int(rhs[j])] != rhsMaxDegree { j += 1 }

    let result = wreath(lhs, ii, i, rhs, jj, j, degrees: degrees, perm: perm)
    if result != .equal { return result }

    i += 1; ii = i
    j += 1; jj = j
  }

  precondition(j == rhsTo)
  return .equal
}

func compare(_ lhs: Word, _ rhs: Word, order: Order) -> CompareResult {
  switch order {
  case .shortlex:
    if lhs.count != rhs.count {
      return lhs.count < rhs.count ? .lessThan : .greaterThan
    }

    for i in lhs.indices {
      let x = lhs[i]
      let y = rhs[i]
      if x != y {
        return x < y ? .lessThan : .greaterThan
      }
    }

    return .equal

  case .permutation(let perm):
    return shortlex(lhs, 0, lhs.count, rhs, 0, rhs.count, perm: perm)

  case .wreath(let degrees, let perm):
    return wreath(lhs, 0, lhs.count, rhs, 0, rhs.count,
                  degrees: degrees, perm: perm)
  }
}

func compare(_ lhs: Rule, _ rhs: Rule, order: Order) -> CompareResult {
  let result = compare(lhs.lhs, rhs.lhs, order: order)
  if result != .equal {
    return result
  }

  return compare(lhs.rhs, rhs.rhs, order: order)
}

extension Rule {
  func oriented(order: Order) -> Rule? {
    switch compare(lhs, rhs, order: order) {
    case .equal:
      return nil
    case .lessThan:
      return Rule(lhs: rhs, rhs: lhs)
    case .greaterThan:
      return self
    }
  }
}

extension Presentation {
  func sorted(order: Order) -> Presentation {
    let sortedRules =
      rules.map { $0.oriented(order: order)! }
           .sorted { compare($0, $1, order: order) == .lessThan }
    return Presentation(alphabet: alphabet, rules: sortedRules)
  }
}
