package com.wbillingsley.veautiful

import org.scalajs.dom
import org.scalajs.dom.{Event, html}

case class Lstnr(`type`:String, func:Event => _, usCapture:Boolean=true)
case class AttrVal(name:String, value:String)
case class InlineStyle(name:String, value:String)
case class EvtListener[T](`type`:String, f:Function[T, _], capture:Boolean)

case class DElement(name:String, uniqEl:Any = "") extends DiffNode {

  var children:Seq[VNode] = Seq.empty

  var attributes:Map[String, AttrVal] = Map.empty

  var listeners:Map[String, Lstnr] = Map.empty

  var styles:Seq[InlineStyle] = Seq.empty

  override var domNode:Option[dom.Element] = None

  def domEl = domNode.collect({ case e:dom.Element => e })

  val updateSelf = {
    case el:DElement =>
      removeAttrsFromNode(attributes.values)
      attributes = el.attributes
      applyAttrsToNode(attributes.values)

      removeStylesFromNode(styles)
      styles = el.styles
      applyStylesToNode(styles)

      removeLsntrsFromNode(listeners.values)
      listeners = el.listeners
      applyLsntrsToNode(listeners.values)
  }

  def applyAttrsToNode(as:Iterable[AttrVal]):Unit = {
    for { n <- domEl; a <- as } {
      n.setAttribute(a.name, a.value)
    }
  }

  def removeAttrsFromNode(as:Iterable[AttrVal]):Unit = {
    for { n <- domEl; a <- as } {
      n.removeAttribute(a.name)
    }
  }

  def applyLsntrsToNode(as:Iterable[Lstnr]):Unit = {
    for { n <- domEl; a <- as } {
      n.addEventListener(a.`type`, a.func, true)
    }
  }

  def removeLsntrsFromNode(as:Iterable[Lstnr]):Unit = {
    for { n <- domEl; a <- as } {
      n.removeEventListener(a.`type`, a.func, true)
    }
  }

  def style(s:InlineStyle*) = {
    styles ++= s
    this
  }

  def applyStylesToNode(as:Iterable[InlineStyle]):Unit = {
    domEl match {
      case Some(h:html.Element) => as.foreach({x => h.style.setProperty(x.name, x.value) })
      case _ => // nothing
    }
  }

  def removeStylesFromNode(as:Iterable[InlineStyle]):Unit = {
    domEl match {
      case Some(h:html.Element) => as.foreach({x => h.style.removeProperty(x.name) })
      case _ => // nothing
    }
  }

  def attrs(attrs:AttrVal*) = {
    attributes ++= attrs.map({ x => x.name -> x }).toMap
    this
  }

  def children(ac:VNode*):DElement = {
    children ++= ac
    this
  }

  def apply(ac: <.DElAppliable *):DElement = {
    attrs(ac.collect({ case attr: <.DEAAttr => attr.a }):_*)
    on(ac.collect({ case attr: <.DEALstnr => attr.l }):_*)
    style(ac.collect({ case attr: <.DEAStyle => attr.s }):_*)
    children(ac.collect({ case vn: <.DEAVNode => vn.vNode }):_*)
  }

  def on(l: Lstnr *) = {
    listeners ++= l.map({x => x.`type` -> x }).toMap
    this
  }


  def create() = {
    val e = dom.document.createElement(name)

    for { AttrVal(a, value) <- attributes.values } {
      e.setAttribute(a, value)
    }

    for { Lstnr(t, f, cap) <- listeners.values } {
      e.addEventListener(t, f, cap)
    }

    applyStylesToNode(styles)

    e
  }


}

case class Text(text:String) extends VNode {

  var domNode:Option[dom.Node] = None

  def create() = {
    dom.document.createTextNode(text)
  }

  def attach() = {
    val n = create()
    domNode = Some(n)
    n
  }

  def detach() = {
    domNode = None
  }

}

object < {

  trait DElAppliable
  implicit class DEAVNode(val vNode: VNode) extends DElAppliable
  implicit class DEAAttr(val a: AttrVal) extends DElAppliable
  implicit class DEALstnr(val l: Lstnr) extends DElAppliable
  implicit class DEAStyle(val s:InlineStyle) extends DElAppliable
  implicit def DEAText(t: String):DElAppliable = new DEAVNode(Text(t))


  def p = apply("p")
  def div = apply("div")
  def img = apply("img")
  def a = apply("a")
  def span = apply("span")
  def h1 = apply("h1")
  def h2 = apply("h2")
  def h3 = apply("h3")
  def h4 = apply("h4")

  def ol = apply("ol")
  def ul = apply("ul")
  def li = apply("li")

  def table = apply("table")
  def thead = apply("thead")
  def tbody = apply("tbody")
  def tr = apply("tr")
  def th = apply("th")
  def td = apply("td")


  def apply(n:String, u:String = "") = DElement(n, u)

}

object ^ {

  case class Attrable(n:String) {
    def :=(s:String) = AttrVal(n, s)
  }

  def target = Attrable("target")
  def style = Attrable("style")
  def alt = Attrable("alt")
  def src = Attrable("src")
  def `class` = Attrable("class")
  def cls = `class`
  def role = Attrable("role")
  def href = Attrable("href")

  case class Lsntrable(n:String) {
    def -->(e: => Unit ) = Lstnr(n, (x:Event) => e, true)
  }

  def onClick = Lsntrable("click")

  case class InlineStylable(n:String) {
    def :=(v:String) = InlineStyle(n, v)
  }

  def minHeight = InlineStylable("min-height")
  def backgroundImage = InlineStylable("background-image")

}