//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   11 Jul 08  Brian Frank  Creation
//

**
** Pen defines how a shape is stroked.
**
@Js
@Serializable { simple = true }
const class Pen
{

  **
  ** Width of the stroke, default is 1.
  **
  const Int width := 1

  **
  ** Defines how two ends of unjoined segements are stroked.
  ** Valid values are `capSquare`, `capButt`, and `capRound`.
  ** Default is capSquare.
  **
  const Int cap := capSquare

  ** Constant for `cap`
  static const Int capSquare := 0
  ** Constant for `cap`
  static const Int capButt  := 1
  ** Constant for `cap`
  static const Int capRound := 2

  **
  ** Defines how two path segments are joined at the endpoints.
  ** Valid values are `joinMiter`, `joinRound`, and `joinBevel`.
  ** Default is joinMiter.
  **
  const Int join := joinMiter

  ** Constant for `join`
  static const Int joinMiter := 0
  ** Constant for `join`
  static const Int joinBevel := 1
  ** Constant for `join`
  static const Int joinRound := 3

  **
  ** Dash pattern as on/off again lengths.  If null
  ** then shapes are stroked solid.
  **
  ** Note: dashes not supported in javascript
  **
  const Int[]? dash

  **
  ** Default pen is width of 1 with capSquare and joinMiter.
  **
  static const Pen defVal := make {}

  **
  ** Construct with it-block
  **
  new make(|This| f) { f(this) }

  **
  ** Parse a pen from string (see `toStr`).  If invalid
  ** and checked is true then throw ParseErr otherwise
  ** return null.
  **
  static new fromStr(Str str, Bool checked := true)
  {
    try
    {
      Int? w := null
      c := capSquare
      j := joinMiter
      Int[]? d := null

      b := str.index("[")
      if (b != null)
      {
        d = Int[,]
        str[b+1..<str.index("]")].split(',').each |Str tok| { d.add(tok.toInt) }
        str = str[0..<b].trim
      }

      str.split.each |Str s|
      {
        switch (s)
        {
          case "capSquare": c = capSquare
          case "capButt":   c = capButt
          case "capRound":  c = capRound
          case "joinMiter": j = joinMiter
          case "joinBevel": j = joinBevel
          case "joinRound": j = joinRound
          default:          w = s.toInt
        }
      }

      return Pen { it.width = w; it.cap = c; it.join = j; it.dash = d }
    }
    catch {}
    if (checked) throw ParseErr("Invalid Pen: $str")
    return null
  }

  **
  ** Hash the fields.
  **
  override Int hash()
  {
    h := width.xor(cap.shiftl(16)).xor(join.shiftl(20))
    if (dash != null) h = h.xor(dash.hash.shiftl(32))
    return h
  }

  **
  ** Equality is based on Pen's fields.
  **
  override Bool equals(Obj? obj)
  {
    that := obj as Pen
    if (that == null) return false
    return this.width == that.width &&
           this.cap   == that.cap   &&
           this.join  == that.join  &&
           this.dash  == that.dash
  }

  **
  ** Return "square", "butt", "round"
  **
  Str capToStr()
  {
    switch (cap)
    {
      case capSquare: return "square"
      case capButt:   return "butt"
      case capRound:  return "round"
      default:        throw Err()
    }
  }

  **
  ** Return "miter", "round", "bevel"
  **
  Str joinToStr()
  {
    switch (join)
    {
      case joinMiter: return "miter"
      case joinRound: return "round"
      case joinBevel: return "bevel"
      default:        throw Err()
    }
  }

  **
  ** Return '"width cap join dash"' such as '"2 capButt joinBevel [1,1]"'.
  ** Omit cap, join, or dash if at defaults.
  **
  override Str toStr()
  {
    s := width.toStr

    switch (cap)
    {
      case capButt:  s += " capButt"
      case capRound: s += " capRound"
    }

    switch (join)
    {
      case joinBevel: s += " joinBevel"
      case joinRound: s += " joinRound"
    }

    if (dash != null) s += " [" + dash.join(",") + "]"
    return s
  }

}