/* NSC -- new Scala compiler
 * Copyright 2005-2009 LAMP/EPFL
 * Copyright 2007 Google Inc. All Rights Reserved.
 * Author: bqe@google.com (Burak Emir)
 */
// $Id: TransMatcher.scala 18387 2009-07-24 15:28:37Z odersky $

/**
 * Simple pattern types:
 *
 * 1 Variable               x
 * 3 Literal                56
 *
 * Types which must be decomposed into conditionals and simple types:
 *
 * 2 Typed                  x: Int
 * 4 Stable Identifier      Bob
 * 5 Constructor            Symbol("abc")
 * 6 Tuple                  (5, 5)
 * 7 Extractor              List(1, 2)
 * 8 Sequence               List(1, 2, _*)
 * 9 Infix                  5 :: xs
 * 10 Alternative           "foo" | "bar"
 * 11 XML                   --
 * 12 Regular Expression    --
 */

package scala.tools.nsc
package matching

import util.Position
import ast.{ TreePrinters, Trees }
import symtab.SymbolTable
import java.io.{ StringWriter, PrintWriter }
import scala.util.NameTransformer.decode

/** Translation of pattern matching
 *
 *  @author Burak Emir
 */
trait TransMatcher extends ast.TreeDSL with CompactTreePrinter {
  self: transform.ExplicitOuter with PatternNodes with ParallelMatching => 

  import global.{ typer => _, _ }
  import analyzer.Typer;
  import definitions._
  import symtab.Flags
  import CODE._

  var cunit: CompilationUnit = _  // memory leak?
  
  def newName(pos: Position, s: String) = cunit.fresh.newName(pos, s)

  final val settings_squeeze = settings.Xsqueeze.value == "on"
  
  /** Contains data structures which the match algorithm implementation
   *  requires but which aren't essential to the algorithm itself.
   */
  case class MatchMatrixContext(
    handleOuter: TreeFunction1,   // Tree => Tree function 
    typer: Typer,                 // a local typer
    owner: Symbol,                // the current owner
    resultType: Type)             // the expected result type of the whole match
  {  
    def newVar(
      pos: Position,
      tpe: Type,
      flags: List[Long] = Nil,
      name: Name = null): Symbol =
    {
      val n: Name = if (name == null) newName(pos, "temp") else name
      // careful: pos has special meaning 
      owner.newVariable(pos, n) setInfo tpe setFlag (0L /: flags)(_|_)
    }
    
    def typedValDef(x: Symbol, rhs: Tree) = {
      val finalRhs = x.tpe match {
        case WildcardType   =>
          rhs setType null
          x setInfo (typer typed rhs).tpe
          rhs
        case _              =>
          typer.typed(rhs, x.tpe)
      }
      typer typed (VAL(x) === finalRhs)
    }
    
    def squeezedBlock(vds: List[Tree], exp: Tree): Tree =
      if (settings_squeeze) Block(Nil, squeezedBlock1(vds, exp))
      else                  Block(vds, exp)

    private def squeezedBlock1(vds: List[Tree], exp: Tree): Tree = {
      class RefTraverser(sym: Symbol) extends Traverser {
        var nref, nsafeRef = 0
        override def traverse(tree: Tree) = tree match {
          case t: Ident if t.symbol eq sym => 
            nref += 1
            if (sym.owner == currentOwner) // oldOwner should match currentOwner
              nsafeRef += 1

          case LabelDef(_, args, rhs) =>
            (args dropWhile(_.symbol ne sym)) match {
              case Nil  => 
              case _    => nref += 2  // cannot substitute this one
            }
            traverse(rhs)
          case t if nref > 1 =>       // abort, no story to tell
          case t =>
            super.traverse(t)
        }
      }

      class Subst(sym: Symbol, rhs: Tree) extends Transformer {
        var stop = false
        override def transform(tree: Tree) = tree match {
          case t: Ident if t.symbol == sym => 
            stop = true
            rhs
          case _ => if (stop) tree else super.transform(tree)
        }
      }

      lazy val squeezedTail = squeezedBlock(vds.tail, exp)
      def default = squeezedTail match {
        case Block(vds2, exp2) => Block(vds.head :: vds2, exp2)  
        case exp2              => Block(vds.head :: Nil,  exp2)  
      }

      if (vds.isEmpty) exp 
      else vds.head match {
        case vd: ValDef =>
          val sym = vd.symbol
          val rt = new RefTraverser(sym)
          rt.atOwner (owner) (rt traverse squeezedTail)

          rt.nref match {
            case 0                      => squeezedTail
            case 1 if rt.nsafeRef == 1  => new Subst(sym, vd.rhs) transform squeezedTail
            case _                      => default
          }
        case _          =>
          default
      }
    }
  }
    
  case class MatchMatrixInit(
    roots: List[Symbol],
    cases: List[CaseDef],
    default: Tree
  )

  /** Handles all translation of pattern matching.
   */
  def handlePattern(
    selector: Tree,         // tree being matched upon (called scrutinee after this)
    cases: List[CaseDef],   // list of cases in the match
    isChecked: Boolean,     // whether exhaustiveness checking is enabled (disabled with @unchecked)
    context: MatchMatrixContext): Tree =
  {
    import context._
    
    // TRANS_FLAG communicates that there should be no exhaustiveness checking
    val flags                 = List(Flags.TRANS_FLAG) filterNot (_ => isChecked)    
    def matchError(obj: Tree) = atPos(selector.pos)(THROW(MatchErrorClass, obj))
    def caseIsOk(c: CaseDef)  = cond(c.pat) { case _: Apply | Ident(nme.WILDCARD) => true }

    // this appears to be an attempt at optimizing when all case defs are constructor
    // patterns, but I don't think it's correct.
    def doApply(fn: Tree): Boolean =
      (fn.symbol eq (selector.tpe.decls lookup nme.CONSTRUCTOR)) &&
      (cases forall caseIsOk)
      
    // For x match { ... we start with a single root
    def singleMatch(): (List[Tree], MatchMatrixInit) = {
      val root: Symbol      = newVar(selector.pos, selector.tpe, flags)
      val varDef: Tree      = typedValDef(root, selector)
      
      (List(varDef), MatchMatrixInit(List(root), cases, matchError(ID(root))))
    }
    
    // For (x, y, z) match { ... we start with multiple roots, called tpXX.
    def tupleMatch(app: Apply): (List[Tree], MatchMatrixInit) = {
      val Apply(fn, args) = app
      val (roots, vars) = List.unzip(
        for ((arg, typeArg) <- args zip selector.tpe.typeArgs) yield {
          val v = newVar(arg.pos, typeArg, flags, newName(arg.pos, "tp"))
          (v, typedValDef(v, arg))
        }
      )
      (vars, MatchMatrixInit(roots, cases, matchError(treeCopy.Apply(app, fn, roots map ID))))
    }

    // sets up top level variables and algorithm input
    val (vars, matrixInit) = selector match {
      case app @ Apply(fn, _) if isTupleType(selector.tpe) && doApply(fn) => tupleMatch(app)
      case _                                                              => singleMatch()
    }
    
    val matrix  = new MatchMatrix(context, matrixInit)
    val rep     = matrix.expansion                    // expands casedefs and assigns name
    val mch     = typer typed rep.toTree              // executes algorithm, converts tree to DFA
    val dfatree = typer typed Block(vars, mch)        // packages into a code block

    // TRACE("handlePattern(\n  roots = %s\n  cases = %s\n  rep = %s\n  initRep = %s\n)", 
    //   roots, cases.mkString("(\n    ", "\n    ", "\n)"), rep, irep)
    // TRACE("dfatree(1) = " + toCompactString(dfatree))

    // redundancy check
    for ((cs, bx) <- cases.zipWithIndex)
      if (!matrix.isReached(bx)) cunit.error(cs.body.pos, "unreachable code")
      
    // cleanup performs squeezing and resets any remaining TRANS_FLAGs
    matrix cleanup dfatree
  }
  
  private def toCompactString(t: Tree): String = {
    val buffer = new StringWriter()
    val printer = compactTreePrinters.create(new PrintWriter(buffer))
    printer.print(t)
    printer.flush()
    buffer.toString
  }
}

/** A tree printer which is stingier about vertical whitespace and unnecessary
 *  punctuation than the standard one.
 */
trait CompactTreePrinter {
  val global: Global
  
  object compactTreePrinters extends {
    val trees: global.type = global
  } with TreePrinters {
    import trees._
    
    override def create(writer: PrintWriter): TreePrinter = new TreePrinter(writer) {
      // drill down through Blocks and pull out the real statements.
      def allStatements(t: Tree): List[Tree] = t match {
        case Block(stmts, expr) => (stmts flatMap allStatements) ::: List(expr)
        case _                  => List(t)
      }

      override def printRaw(tree: Tree): Unit = {
        // routing supercalls through this for debugging ease
        def s() = {
          // Console.println("toSuper: " + tree.getClass)
          super.printRaw(tree)
        }
        
        tree match {
          // labels used for jumps - does not map to valid scala code
          case LabelDef(name, params, rhs) =>
            print("labeldef %s(%s) = ".format(name, params mkString ","))
            printRaw(rhs)
            
          // target.method(arg) ==> target method arg
          case Apply(Select(target, method), List(arg)) =>
            (target, arg) match {
              case (_: Ident, _: Literal | _: Ident)  => 
                printRaw(target)
                print(" %s " format symName(tree, method))
                printRaw(arg)
              case _                        => s()
            }
            
          // case Select(Select(_, x), y) if x.toString == "this" =>
          //   print(symName(tree, y))
          // target.unary_! ==> !target
          case Select(qualifier, name) =>
            val n = symName(tree, name)          
            if (n startsWith "unary_") {
              print(n drop 6)
              print(qualifier)
            }
            else s()
            
          // target.toString() ==> target.toString
          case Apply(fn, Nil)   => printRaw(fn)
          
          // if a Block only continues one actual statement, just print it.
          case Block(stats, expr) =>          
            allStatements(tree) match {
              case List(x)            => printRow(List(x), "", ";", "")
              case _                  => s()
            }
          
          // If thenp or elsep has only one statement, it doesn't need more than one line.
          case If(cond, thenp, elsep) =>
            printRow(List(cond), "if (", "", ") ")
            
            def ifIndented(x: Tree) = {
              indent ; println ; printRaw(x) ; undent
            }
        
            indent ; println ; 
            allStatements(thenp) match {
              case List(x: If)  => ifIndented(x)
              case List(x)      => printRaw(x)
              case _            => printRaw(thenp)
            }
            undent ; println ; 
            val elseStmts = allStatements(elsep)
            if (!elseStmts.isEmpty) {
              print("else")
              indent ; println 
              elseStmts match {
                case List(x)      => printRaw(x)
                case xs           => printRaw(elsep)
              }
              undent ; println
            }
          case _        => s()
        }
      }      
    }
  }
  
  lazy val compactTreePrinter = compactTreePrinters.create()
}