/** Code generation of the AnyVal types and their companions. */
trait GenerateAnyValReps {
  self: GenerateAnyVals =>

  sealed abstract class AnyValNum(name: String, repr: Option[String], javaEquiv: String)
      extends AnyValRep(name,repr,javaEquiv) {

    case class Op(op : String, doc : String)

    private def companionCoercions(tos: AnyValRep*) = {
      tos.toList map (to =>
        s"implicit def @javaequiv@2${to.javaEquiv}(x: @name@): ${to.name} = x.to${to.name}"
      )
    }
    def coercionComment =
"""/** Language mandated coercions from @name@ to "wider" types. */
import scala.language.implicitConversions"""

    def implicitCoercions: List[String] = {
      val coercions = this match {
        case B     => companionCoercions(S, I, L, F, D)
        case S | C => companionCoercions(I, L, F, D)
        case I     => companionCoercions(L, F, D)
        case L     => companionCoercions(F, D)
        case F     => companionCoercions(D)
        case _     => Nil
      }
      if (coercions.isEmpty) Nil
      else coercionComment.lines.toList ++ coercions
    }

    def isCardinal: Boolean = isIntegerType(this)
    def unaryOps = {
      val ops = List(
        Op("+", "/** Returns this value, unmodified. */"),
        Op("-", "/** Returns the negation of this value. */"))

      if(isCardinal)
        Op("~", "/**\n" +
                " * Returns the bitwise negation of this value.\n" +
                " * @example {{{\n" +
                " * ~5 == -6\n" +
                " * // in binary: ~00000101 ==\n" +
                " * //             11111010\n" +
                " * }}}\n" +
                " */") :: ops
      else ops
    }

    def bitwiseOps =
      if (isCardinal)
        List(
          Op("|", "/**\n" +
                     "  * Returns the bitwise OR of this value and `x`.\n" +
                     "  * @example {{{\n" +
                     "  * (0xf0 | 0xaa) == 0xfa\n" +
                     "  * // in binary:   11110000\n" +
                     "  * //            | 10101010\n" +
                     "  * //              --------\n" +
                     "  * //              11111010\n" +
                     "  * }}}\n" +
                     "  */"),
          Op("&", "/**\n" +
                     "  * Returns the bitwise AND of this value and `x`.\n" +
                     "  * @example {{{\n" +
                     "  * (0xf0 & 0xaa) == 0xa0\n" +
                     "  * // in binary:   11110000\n" +
                     "  * //            & 10101010\n" +
                     "  * //              --------\n" +
                     "  * //              10100000\n" +
                     "  * }}}\n" +
                     "  */"),
          Op("^", "/**\n" +
                     "  * Returns the bitwise XOR of this value and `x`.\n" +
                     "  * @example {{{\n" +
                     "  * (0xf0 ^ 0xaa) == 0x5a\n" +
                     "  * // in binary:   11110000\n" +
                     "  * //            ^ 10101010\n" +
                     "  * //              --------\n" +
                     "  * //              01011010\n" +
                     "  * }}}\n" +
                     "  */"))
      else Nil

    def shiftOps =
      if (isCardinal)
        List(
          Op("<<",  "/**\n" +
                       "  * Returns this value bit-shifted left by the specified number of bits,\n" +
                       "  *         filling in the new right bits with zeroes.\n" +
                       "  * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}}\n" +
                       "  */"),

          Op(">>>", "/**\n" +
                       "  * Returns this value bit-shifted right by the specified number of bits,\n" +
                       "  *         filling the new left bits with zeroes.\n" +
                       "  * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}}\n" +
                       "  * @example {{{\n" +
                       "  * -21 >>> 3 == 536870909\n" +
                       "  * // in binary: 11111111 11111111 11111111 11101011 >>> 3 ==\n" +
                       "  * //            00011111 11111111 11111111 11111101\n" +
                       "  * }}}\n" +
                       "  */"),

          Op(">>",  "/**\n" +
                       "  * Returns this value bit-shifted right by the specified number of bits,\n" +
                       "  *         filling in the left bits with the same value as the left-most bit of this.\n" +
                       "  *         The effect of this is to retain the sign of the value.\n" +
                       "  * @example {{{\n" +
                       "  * -21 >> 3 == -3\n" +
                       "  * // in binary: 11111111 11111111 11111111 11101011 >> 3 ==\n" +
                       "  * //            11111111 11111111 11111111 11111101\n" +
                       "  * }}}\n" +
                       "  */"))
      else Nil

    def comparisonOps = List(
      Op("==", "/** Returns `true` if this value is equal to x, `false` otherwise. */"),
      Op("!=", "/** Returns `true` if this value is not equal to x, `false` otherwise. */"),
      Op("<",  "/** Returns `true` if this value is less than x, `false` otherwise. */"),
      Op("<=", "/** Returns `true` if this value is less than or equal to x, `false` otherwise. */"),
      Op(">",  "/** Returns `true` if this value is greater than x, `false` otherwise. */"),
      Op(">=", "/** Returns `true` if this value is greater than or equal to x, `false` otherwise. */"))

    def otherOps = List(
      Op("+", "/** Returns the sum of this value and `x`. */"),
      Op("-", "/** Returns the difference of this value and `x`. */"),
      Op("*", "/** Returns the product of this value and `x`. */"),
      Op("/", "/** Returns the quotient of this value and `x`. */"),
      Op("%", "/** Returns the remainder of the division of this value by `x`. */"))

    // Given two numeric value types S and T , the operation type of S and T is defined as follows:
    // If both S and T are subrange types then the operation type of S and T is Int.
    // Otherwise the operation type of S and T is the larger of the two types wrt ranking.
    // Given two numeric values v and w the operation type of v and w is the operation type
    // of their run-time types.
    def opType(that: AnyValNum): AnyValNum = {
      val rank = IndexedSeq(I, L, F, D)
      (rank indexOf this, rank indexOf that) match {
        case (-1, -1)   => I
        case (r1, r2)   => rank apply (r1 max r2)
      }
    }

    def mkCoercions = numeric map (x => "def to%s: %s".format(x, x))
    def mkUnaryOps  = unaryOps map (x => "%s\n  def unary_%s : %s".format(x.doc, x.op, this opType I))
    def mkStringOps = List("def +(x: String): String")
    def mkShiftOps  = (
      for (op <- shiftOps ; arg <- List(I, L)) yield
        "%s\n  def %s(x: %s): %s".format(op.doc, op.op, arg, this opType I)
    )

    def clumps: List[List[String]] = {
      val xs1 = List(mkCoercions, mkUnaryOps, mkStringOps, mkShiftOps) map (xs => if (xs.isEmpty) xs else xs :+ "")
      val xs2 = List(
        mkBinOpsGroup(comparisonOps, numeric, _ => Z),
        mkBinOpsGroup(bitwiseOps, cardinal, this opType _),
        mkBinOpsGroup(otherOps, numeric, this opType _)
      )
      xs1 ++ xs2
    }
    def classLines = (clumps :+ commonClassLines).foldLeft(List[String]()) {
      case (res, Nil)   => res
      case (res, lines) =>
        val xs = lines map {
          case ""   => ""
          case s    => interpolate(s)
        }
        res ++ xs
    }
    def objectLines = {
      val comp = if (isCardinal) cardinalCompanion else floatingCompanion
      interpolate(comp + allCompanions + "\n" + nonUnitCompanions).trim.lines.toList ++ (implicitCoercions map interpolate)
    }

    /** Makes a set of binary operations based on the given set of ops, args, and resultFn.
     *
     *  @param    ops       list of function names e.g. List(">>", "%")
     *  @param    args      list of types which should appear as arguments
     *  @param    resultFn  function which calculates return type based on arg type
     *  @return             list of function definitions
     */
    def mkBinOpsGroup(ops: List[Op], args: List[AnyValNum], resultFn: AnyValNum => AnyValRep): List[String] = (
      ops flatMap (op =>
        args.map(arg =>
          "%s\n  def %s(x: %s): %s".format(op.doc, op.op, arg, resultFn(arg))) :+ ""
      )
    ).toList
  }

  sealed abstract class AnyValRep(val name: String, val repr: Option[String], val javaEquiv: String) {
    def classLines: List[String]
    def objectLines: List[String]
    def commonClassLines = List(
      "// Provide a more specific return type for Scaladoc",
      "override def getClass(): Class[@name@] = ???"
    )

    def lcname = name.toLowerCase
    def boxedSimpleName = this match {
      case C => "Character"
      case I => "Integer"
      case _ => name
    }
    def boxedName = this match {
      case U => "scala.runtime.BoxedUnit"
      case _ => "java.lang." + boxedSimpleName
    }
    def zeroRep = this match {
      case L => "0L"
      case F => "0.0f"
      case D => "0.0d"
      case _ => "0"
    }

    def representation = repr.map(", a " + _).getOrElse("")

    def indent(s: String)  = if (s == "") "" else "  " + s
    def indentN(s: String) = s.lines map indent mkString "\n"

    def boxUnboxInterpolations = Map(
      "@boxRunTimeDoc@" -> """
 *  Runtime implementation determined by `scala.runtime.BoxesRunTime.boxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]].
 *""".format(boxedSimpleName),
      "@unboxRunTimeDoc@" -> """
 *  Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]].
 *""".format(name),
      "@unboxDoc@" -> "the %s resulting from calling %sValue() on `x`".format(name, lcname),
      "@boxImpl@" -> "???",
      "@unboxImpl@" -> "???"
    )
    def interpolations = Map(
      "@name@"      -> name,
      "@representation@" -> representation,
      "@javaequiv@" -> javaEquiv,
      "@boxed@"     -> boxedName,
      "@lcname@"    -> lcname,
      "@zero@"      -> zeroRep
    ) ++ boxUnboxInterpolations

    def interpolate(s: String): String = interpolations.foldLeft(s) {
      case (str, (key, value)) => str.replaceAll(key, value)
    }
    def classDoc  = interpolate(classDocTemplate)
    def objectDoc = ""
    def mkImports = ""

    def mkClass       = assemble("final abstract class " + name + " private extends AnyVal", classLines)
    def mkObject      = assemble("object " + name + " extends AnyValCompanion", objectLines)
    def make()    = List[String](
      headerTemplate,
      mkImports,
      classDoc,
      mkClass,
      objectDoc,
      mkObject
    ) mkString ""

    def assemble(decl: String, lines: List[String]): String = {
      val body = if (lines.isEmpty) " { }\n\n" else lines map indent mkString (" {\n", "\n", "\n}\n")

      decl + body + "\n"
    }
    override def toString = name
  }
}

trait GenerateAnyValTemplates {
  def headerTemplate = """/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2002-2013, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

// DO NOT EDIT, CHANGES WILL BE LOST
// This auto-generated code can be modified in "project/GenerateAnyVals.scala".
// Afterwards, running "sbt generateSources" regenerates this source file.

package scala

"""

  def classDocTemplate = ("""
/** `@name@`@representation@ (equivalent to Java's `@javaequiv@` primitive type) is a
 *  subtype of [[scala.AnyVal]]. Instances of `@name@` are not
 *  represented by an object in the underlying runtime system.
 *
 *  There is an implicit conversion from [[scala.@name@]] => [[scala.runtime.Rich@name@]]
 *  which provides useful non-primitive operations.
 */
""".trim + "\n")

  def allCompanions = """
/** Transform a value type into a boxed reference type.
 *@boxRunTimeDoc@
 *  @param  x   the @name@ to be boxed
 *  @return     a @boxed@ offering `x` as its underlying value.
 */
def box(x: @name@): @boxed@ = @boxImpl@

/** Transform a boxed type into a value type.  Note that this
 *  method is not typesafe: it accepts any Object, but will throw
 *  an exception if the argument is not a @boxed@.
 *@unboxRunTimeDoc@
 *  @param  x   the @boxed@ to be unboxed.
 *  @throws     ClassCastException  if the argument is not a @boxed@
 *  @return     @unboxDoc@
 */
def unbox(x: java.lang.Object): @name@ = @unboxImpl@

/** The String representation of the scala.@name@ companion object. */
override def toString = "object scala.@name@"
"""

  def nonUnitCompanions = ""  // todo

  def cardinalCompanion = """
/** The smallest value representable as a @name@. */
final val MinValue = @boxed@.MIN_VALUE

/** The largest value representable as a @name@. */
final val MaxValue = @boxed@.MAX_VALUE
"""

  def floatingCompanion = """
/** The smallest positive value greater than @zero@ which is
 *  representable as a @name@.
 */
final val MinPositiveValue = @boxed@.MIN_VALUE
final val NaN              = @boxed@.NaN
final val PositiveInfinity = @boxed@.POSITIVE_INFINITY
final val NegativeInfinity = @boxed@.NEGATIVE_INFINITY

/** The negative number with the greatest (finite) absolute value which is representable
 *  by a @name@.  Note that it differs from [[java.lang.@name@.MIN_VALUE]], which
 *  is the smallest positive value representable by a @name@.  In Scala that number
 *  is called @name@.MinPositiveValue.
 */
final val MinValue = -@boxed@.MAX_VALUE

/** The largest finite positive number representable as a @name@. */
final val MaxValue = @boxed@.MAX_VALUE
"""
}

class GenerateAnyVals extends GenerateAnyValReps with GenerateAnyValTemplates {
  object B extends AnyValNum("Byte",    Some("8-bit signed integer"),                  "byte")
  object S extends AnyValNum("Short",   Some("16-bit signed integer"),                 "short")
  object C extends AnyValNum("Char",    Some("16-bit unsigned integer"),               "char")
  object I extends AnyValNum("Int",     Some("32-bit signed integer"),                 "int")
  object L extends AnyValNum("Long",    Some("64-bit signed integer"),                 "long")
  object F extends AnyValNum("Float",   Some("32-bit IEEE-754 floating point number"), "float")
  object D extends AnyValNum("Double",  Some("64-bit IEEE-754 floating point number"), "double")
  object Z extends AnyValRep("Boolean", None,                                          "boolean") {
    def classLines = """
/** Negates a Boolean expression.
  *
  * - `!a` results in `false` if and only if `a` evaluates to `true` and
  * - `!a` results in `true` if and only if `a` evaluates to `false`.
  *
  * @return the negated expression
  */
def unary_! : Boolean

/** Compares two Boolean expressions and returns `true` if they evaluate to the same value.
  *
  * `a == b` returns `true` if and only if
  *  - `a` and `b` are `true` or
  *  - `a` and `b` are `false`.
  */
def ==(x: Boolean): Boolean

/**
  * Compares two Boolean expressions and returns `true` if they evaluate to a different value.
  *
  * `a != b` returns `true` if and only if
  *  - `a` is `true` and `b` is `false` or
  *  - `a` is `false` and `b` is `true`.
  */
def !=(x: Boolean): Boolean

/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true.
  *
  * `a || b` returns `true` if and only if
  *  - `a` is `true` or
  *  - `b` is `true` or
  *  - `a` and `b` are `true`.
  *
  * @note This method uses 'short-circuit' evaluation and
  *       behaves as if it was declared as `def ||(x: => Boolean): Boolean`.
  *       If `a` evaluates to `true`, `true` is returned without evaluating `b`.
  */
def ||(x: Boolean): Boolean

/** Compares two Boolean expressions and returns `true` if both of them evaluate to true.
  *
  * `a && b` returns `true` if and only if
  *  - `a` and `b` are `true`.
  *
  * @note This method uses 'short-circuit' evaluation and
  *       behaves as if it was declared as `def &&(x: => Boolean): Boolean`.
  *       If `a` evaluates to `false`, `false` is returned without evaluating `b`.
  */
def &&(x: Boolean): Boolean

// Compiler won't build with these seemingly more accurate signatures
// def ||(x: => Boolean): Boolean
// def &&(x: => Boolean): Boolean

/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true.
  *
  * `a | b` returns `true` if and only if
  *  - `a` is `true` or
  *  - `b` is `true` or
  *  - `a` and `b` are `true`.
  *
  * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`.
  */
def |(x: Boolean): Boolean

/** Compares two Boolean expressions and returns `true` if both of them evaluate to true.
  *
  * `a & b` returns `true` if and only if
  *  - `a` and `b` are `true`.
  *
  * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`.
  */
def &(x: Boolean): Boolean

/** Compares two Boolean expressions and returns `true` if they evaluate to a different value.
  *
  * `a ^ b` returns `true` if and only if
  *  - `a` is `true` and `b` is `false` or
  *  - `a` is `false` and `b` is `true`.
  */
def ^(x: Boolean): Boolean

// Provide a more specific return type for Scaladoc
override def getClass(): Class[Boolean] = ???
    """.trim.lines.toList

    def objectLines = interpolate(allCompanions + "\n" + nonUnitCompanions).lines.toList
  }
  object U extends AnyValRep("Unit", None, "void") {
    override def classDoc = """
/** `Unit` is a subtype of [[scala.AnyVal]]. There is only one value of type
 *  `Unit`, `()`, and it is not represented by any object in the underlying
 *  runtime system. A method with return type `Unit` is analogous to a Java
 *  method which is declared `void`.
 */
"""
    def classLines  = List(
      "// Provide a more specific return type for Scaladoc",
      "override def getClass(): Class[Unit] = ???"
    )
    def objectLines = interpolate(allCompanions).lines.toList

    override def boxUnboxInterpolations = Map(
      "@boxRunTimeDoc@" -> "",
      "@unboxRunTimeDoc@" -> "",
      "@unboxDoc@" -> "the Unit value ()",
      "@boxImpl@" -> "scala.runtime.BoxedUnit.UNIT",
      "@unboxImpl@" -> "x.asInstanceOf[scala.runtime.BoxedUnit]"
    )
  }

  def isSubrangeType = Set(B, S, C)
  def isIntegerType  = Set(B, S, C, I, L)
  def isFloatingType = Set(F, D)
  def isWideType     = Set(L, D)

  def cardinal = numeric filter isIntegerType
  def numeric  = List(B, S, C, I, L, F, D)
  def values   = List(U, Z) ++ numeric

  def make() = values map (x => (x.name, x.make()))
}

object GenerateAnyVals {
  def run(outDir: java.io.File) {
    val av = new GenerateAnyVals

    av.make() foreach { case (name, code ) =>
      val file = new java.io.File(outDir, name + ".scala")
      sbt.IO.write(file, code, java.nio.charset.Charset.forName("UTF-8"), false)
    }
  }
}
