//
// Copyright (c) 2022, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   28 Feb 2022  Brian Frank  Creation
//

**
** Stroke defines the how to render shape outlines.
**
@Js
@Serializable { simple = true }
const class Stroke
{
  ** Default value is width 1, no dash, butt cap, miter join.
  static const Stroke defVal := makeFields

  **
  ** Parse from string format:
  **    width [dash] cap join
  **
  ** Examples:
  **   0.5
  **   2 [1, 2]
  **   round radius
  **
  static new fromStr(Str s, Bool checked := true)
  {
    try
    {
      width := 1f
      dash  := null
      cap   := StrokeCap.butt
      join  := StrokeJoin.miter

      Str[]? toks
      bracketStart := s.index("[")
      if (bracketStart != null)
      {
        bracketEnd := s.indexr("]")
        dash = s[bracketStart+1..<bracketEnd]
        toks = s[0..<bracketStart].split.addAll(s[bracketEnd+1..-1].split)
      }
      else
      {
        if (s.isEmpty) throw Err()
        toks = s.split
      }

      toks.each |tok|
      {
        if (tok.isEmpty) return

        char := tok[0]

        if (char.isDigit || char == '.')  { width = Float.fromStr(tok); return }

        tryCap := StrokeCap.fromStr(tok, false)
        if (tryCap != null) { cap = tryCap; return }

        join = StrokeJoin.fromStr(tok, true)
      }

      return makeFields(width, dash, cap, join)
    }
    catch (Err e)
    {
      if (checked) throw ParseErr("Stroke: $s")
      return null
    }
  }

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

  ** Make with fields
  new makeFields(Float width := 1f, Str? dash := null, StrokeCap cap := StrokeCap.butt, StrokeJoin join := StrokeJoin.miter)
  {
    this.width = width
    this.dash  = dash
    this.cap   = cap
    this.join  = join
  }

  ** Stroke width.  Default is 1.
  const Float width := 1f

  ** Dash pattern as space/comma separated numbers of dashes and gaps.
  ** If null then render as solid line.
  const Str? dash

  ** How to render line end caps.  Default is butt.
  const StrokeCap cap := StrokeCap.butt

  ** How to render line joins. Default is miter.
  const StrokeJoin join := StrokeJoin.miter

  ** Hash is based on fields
  override Int hash()
  {
    hash := width.hash
            .xor(cap.ordinal.shiftl(11))
            .xor(join.ordinal.shiftl(13))
    if (dash != null) hash = hash.xor(dash.hash)
    return hash
  }

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

  ** Return this stroke with different width.
  Stroke toSize(Float newWidth)
  {
    if (this.width == newWidth) return this
    return makeFields(newWidth, dash, cap, join)
  }

  ** Return string format - see `fromStr`
  override Str toStr()
  {
    s := StrBuf()
    if (width != 1f) s.join(GeomUtil.formatFloat(width))
    if (dash != null) s.add(" [").add(dash).add("]")
    if (cap !== StrokeCap.butt) s.addChar(' ').add(cap.name)
    if (join !== StrokeJoin.miter) s.addChar(' ').add(join.name)
    if (s.isEmpty) return GeomUtil.formatFloat(width)
    return s.toStr
  }

}

**************************************************************************
** StrokeCap
**************************************************************************

**
** Defines how a stroke end cap is rendered
**
@Js
enum class StrokeCap
{
  ** Cap is a flat edge with no extension
  butt,

  ** Cap is a a rounded semi-circle
  round,

  ** Cap is a half square extension
  square
}

**************************************************************************
** StrokeJoin
**************************************************************************

**
** Defines how two stroke lines are joined together
**
@Js
enum class StrokeJoin
{
  ** Join using a bevel with angle to smooth transition
  bevel,

  ** Join using sharp corners
  miter,

  ** Join using rounded semi-circle (round in SVG terminology)
  radius
}