/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Compiler.IR.Basic
import Lean.Compiler.IR.Format

namespace Lean.IR

def ensureHasDefault (alts : Array Alt) : Array Alt :=
  if alts.any Alt.isDefault then alts
  else if alts.size < 2 then alts
  else
    let last := alts.back;
    let alts := alts.pop;
    alts.push (Alt.default last.body)

private def getOccsOf (alts : Array Alt) (i : Nat) : Nat := Id.run do
  let aBody := (alts.get! i).body
  let mut n := 1
  for j in [i+1:alts.size] do
    if alts[j]!.body == aBody then
      n := n+1
  return n

private def maxOccs (alts : Array Alt) : Alt × Nat := Id.run do
  let mut maxAlt := alts[0]!
  let mut max    := getOccsOf alts 0
  for i in [1:alts.size] do
    let curr := getOccsOf alts i
    if curr > max then
       maxAlt := alts[i]!
       max    := curr
  return (maxAlt, max)

private def addDefault (alts : Array Alt) : Array Alt :=
  if alts.size <= 1 || alts.any Alt.isDefault then alts
  else
    let (max, noccs) := maxOccs alts
    if noccs == 1 then alts
    else
      let alts := alts.filter fun alt => alt.body != max.body
      alts.push (Alt.default max.body)

private def filterUnreachable (alts : Array Alt) : Array Alt :=
  alts.filter fun alt => alt.body != FnBody.unreachable

private def mkSimpCase (tid : Name) (x : VarId) (xType : IRType) (alts : Array Alt) : FnBody :=
  let alts := filterUnreachable alts
  let alts := addDefault alts;
  if alts.size == 0 then
    FnBody.unreachable
  else if alts.size == 1 then
    (alts.get! 0).body
  else
    FnBody.case tid x xType alts

partial def FnBody.simpCase (b : FnBody) : FnBody :=
  let (bs, term) := b.flatten;
  let bs         := modifyJPs bs simpCase;
  match term with
  | FnBody.case tid x xType alts =>
    let alts := alts.map fun alt => alt.modifyBody simpCase;
    reshape bs (mkSimpCase tid x xType alts)
  | _     => reshape bs term

/-- Simplify `case`
  - Remove unreachable branches.
  - Remove `case` if there is only one branch.
  - Merge most common branches using `Alt.default`. -/
def Decl.simpCase (d : Decl) : Decl :=
  match d with
  | .fdecl (body := b) .. => d.updateBody! b.simpCase
  | other => other

end Lean.IR
