/*
 * Copyright 2007-2010 WorldWide Conferencing, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.liftweb {
package http {
package js {
package jquery {

import _root_.scala.xml.{NodeSeq, Group, Unparsed, Elem, Node, SpecialNode}
import _root_.net.liftweb.util.Helpers._
import _root_.net.liftweb.util.Helpers
import _root_.net.liftweb.util.TimeHelpers
import _root_.net.liftweb.common._
import _root_.net.liftweb.util._

import _root_.net.liftweb.http.js.{JsExp, JE}
import JE._
import JsCmds._

/**
 * Classes mixing JQueryRight are also mixing JsMember. As JQueryRight
 * is deprecated clases mixing this trait with stop doing so soon and they
 * will mixin JsMember instead.
 */
@deprecated
trait JQueryRight {
  this: JsExp =>
  def toJsCmd: String
}
/**
 * Classes mixing JQuryLeft will soon stop doing so. Extending/Mixing JsExp will be enough
 */ 
@deprecated
trait JQueryLeft {
  this: JsExp =>
}

object JqJE {
  case object JqScrollToBottom extends JsExp with JsMember with JQueryRight with JQueryLeft {
    def toJsCmd = "each(function(i) {this.scrollTop=this.scrollHeight;})"
  }

  case class JqClick(exp: JsExp) extends JsExp with JsMember with JQueryLeft with JQueryRight {
    def toJsCmd = "click(" + exp.toJsCmd + ")"
  }

  case class JqGetAttr(key: String) extends JsExp with JsMember with JQueryRight with JQueryLeft {
    def toJsCmd = "attr(" + key.encJs + ")"
  }

  /**
   * A JQuery query
   */
  case class Jq(query: JsExp) extends JsExp with JQueryLeft {
    override def toJsCmd = "jQuery(" + query.toJsCmd + ")"
  }

  case object JqDoc extends JsExp with JQueryLeft {
    override def toJsCmd = "jQuery(document)"
  }

  case class JqKeypress(what: (Char, JsCmd)*) extends JsExp with JsMember with JQueryRight {
    override def toJsCmd = "keypress(function(e) {" +
            what.map {
              case (chr, cmd) =>
                "if (e.which == " + chr.toInt + ") {" + cmd.toJsCmd + "}"
            }.mkString(" else \n") +
            "})"
  }

  /**
   * A JQuery query for an element based on the id of the element
   */
  case class JqId(id: JsExp) extends JsExp with JQueryLeft {
    override def toJsCmd = "jQuery('#'+" + id.toJsCmd + ")"
  }

  case class JqAttr(key: String, value: JsExp) extends JsExp with JsMember with JQueryRight with JQueryLeft {
    def toJsCmd = "attr(" + key.encJs + ", " + value.toJsCmd + ")"
  }

  /**
   * Append content to a JQuery
   */
  case class JqAppend(content: NodeSeq) extends JsExp with JsMember with JQueryRight with JQueryLeft {
    override val toJsCmd = "append(" + fixHtml("inline", content) + ")"
  }

  /**
   * Remove JQuery
   */
  case class JqRemove() extends JsExp with JsMember with JQueryRight with JQueryLeft {
    override def toJsCmd = "remove()"
  }


  /**
   * AppendTo content to a JQuery
   */
  case class JqAppendTo(content: NodeSeq) extends JsExp with JsMember with JQueryRight with JQueryLeft {
    override val toJsCmd = "appendTo(" + fixHtml("inline", content) + ")"
  }

  /**
   * Prepend content to a JQuery
   */
  case class JqPrepend(content: NodeSeq) extends JsExp with JsMember with JQueryRight with JQueryLeft {
    override val toJsCmd = "prepend(" + fixHtml("inline", content) + ")"
  }

  /**
   * PrependTo content to a JQuery
   */
  case class JqPrependTo(content: NodeSeq) extends JsExp with JsMember with JQueryRight with JQueryLeft {
    override val toJsCmd = "prependTo(" + fixHtml("inline", content) + ")"
  }

  case class JqCss (name: JsExp, value: JsExp) extends JsExp with JsMember with JQueryRight with JQueryLeft {
    override def toJsCmd = "css(" + name.toJsCmd + "," + value.toJsCmd + ")"
  }

  /**
   * EmptyAfter will empty the node at the given uid and stick the given content behind it. Like
   * a cleaner innerHTML.
   */
  case class JqEmptyAfter(content: NodeSeq) extends JsExp with JsMember with JQueryRight with JQueryLeft {
    override val toJsCmd = "empty().after(" + fixHtml("inline", content) + ")"
  }

  object JqHtml {
    def apply() = new JsExp with JsMember with JQueryRight {
      def toJsCmd = "html()"
    }

    def apply(content: NodeSeq) = new JsExp with JsMember with JQueryRight with JQueryLeft {
      val toJsCmd = "html(" + fixHtml("inline", content) + ")"
    }
  }

  object JqText {
    def apply() = new JsExp with JsMember with JQueryRight {
      def toJsCmd = "text()"
    }

    def apply(content: String) = new JsExp with JsMember with JQueryRight with JQueryLeft {
      def toJsCmd = "text(" + content.encJs + ")"
    }
  }

  /**
   * Serialize input elements intoa string data. ALso works for serializing forms
   */
  case object JqSerialize extends JsExp with JsMember with JQueryRight {
    def toJsCmd = "serialize()"
  }

  /**
   * Serialize the jquery into a JSON array
   */
  case object JsonSerialize extends JsExp with JsMember with JQueryRight {
    def toJsCmd = "serializeArray()"
  }

  case object JqTabsSelected extends JsExp with JsMember with JQueryRight {
    def toJsCmd = "tabsSelected()"
  }

  object JqTabsClick {
    def apply(tab: JsExp): JsExp with JsMember with JQueryRight with JQueryLeft =
      new JsExp with JsMember with JQueryRight with JQueryLeft {
        def toJsCmd = "tabsClick(" + tab.toJsCmd + ")"
      }

    def apply(tab: Int): JsExp with JsMember with JQueryRight with JQueryLeft =
      apply(Num(tab))
  }

  object JqTabs {
    def apply(in: JsExp): JsExp with JsMember with JQueryRight with JQueryLeft =
      new JsExp with JsMember with JQueryRight with JQueryLeft {
        def toJsCmd = "tabs(" + in.toJsCmd + ")"
      }

    def apply(): JsExp with JsMember with JQueryRight with JQueryLeft =
      apply(JsRaw(""))
  }

}

object JqJsCmds {
  implicit def jsExpToJsCmd(in: JsExp) = in.cmd

  case class JqOnLoad(cmd: JsCmd) extends JsCmd {
    def toJsCmd = "jQuery(document).ready(function() {" + cmd.toJsCmd + "});"
  }

  /**
   * Append a NodeSeq to a node specified by uid using jQuery's append() method.
   */
  object AppendHtml {
    def apply(uid: String, content: NodeSeq): JsCmd =
      JqJE.JqId(JE.Str(uid)) ~> JqJE.JqAppend(content)
  }

  /**
   * AppendTo a NodeSeq to a node specified by uid using jQuery's appendTo() method.
   */
  object AppendToHtml {
    def apply(uid: String, content: NodeSeq): JsCmd =
      JqJE.JqId(JE.Str(uid)) ~> JqJE.JqAppendTo(content)
  }

  /**
   * Prepends a NodeSeq to a node specified by uid using jQuery's prepend() method.
   */
  object PrependHtml {
    def apply(uid: String, content: NodeSeq): JsCmd =
      JqJE.JqId(JE.Str(uid)) ~> JqJE.JqPrepend(content)
  }

  /**
   * Replaces the children of the node at  { @code uid } with  { @code content }
   */
  object EmptyAfter {
    def apply(uid: String, content: NodeSeq): JsCmd =
      JqJE.JqId(JE.Str(uid)) ~> JqJE.JqEmptyAfter(content)
  }

  /**
   * Prepends a NodeSeq to a node specified by uid using jQuery prependTo() method.
   */
  object PrependToHtml {
    def apply(uid: String, content: NodeSeq): JsCmd =
      JqJE.JqId(JE.Str(uid)) ~> JqJE.JqPrependTo(content)
  }


  case class JqSetHtml(uid: String, content: NodeSeq) extends JsCmd {
    /**
     * Eagerly evaluate
     */
    val toJsCmd =
    "try{jQuery(" + ("#" + uid).encJs + ").each(function(i) {this.innerHTML = " + fixHtml(uid, content) + ";});} catch (e) {}"
  }

  object Show {
    def apply(uid: String) = new Show(uid, Empty)

    def apply(uid: String, time: TimeSpan) = new Show(uid, Full(time))
  }

  class Show(val uid: String, val time: Box[TimeSpan]) extends JsCmd with HasTime {
    def toJsCmd = "try{jQuery(" + ("#" + uid).encJs + ").show(" + timeStr + ");} catch (e) {}"
  }

  object Hide {
    def apply(uid: String) = new Hide(uid, Empty)

    def apply(uid: String, time: TimeSpan) = new Hide(uid, Full(time))
  }

  class Hide(val uid: String, val time: Box[TimeSpan]) extends JsCmd with HasTime {
    def toJsCmd = "try{jQuery(" + ("#" + uid).encJs + ").hide(" + timeStr + ");} catch (e) {}"
  }

  case class DisplayMessage(where: String, msg: NodeSeq, duration: TimeSpan, fadeTime: TimeSpan) extends JsCmd {
    def toJsCmd = (Show(where) & JqSetHtml(where, msg) & After(duration, Hide(where, fadeTime))).toJsCmd
  }

  /**
  * The companion object to FadeOut that provides an alternative factory
  */
  object FadeOut {
    /**
    * Fade Out with the default duration and fadeTime provided by JsRules
    */
    def apply(id: String) = new FadeOut(id, JsRules.prefadeDuration, JsRules.fadeTime)
  }

  case class FadeOut(id: String, duration: TimeSpan, fadeTime: TimeSpan) extends JsCmd {
    def toJsCmd = (After(duration, JqJE.JqId(id) ~> (new JsRaw("fadeOut(" + fadeTime.millis + ")") with JsMember))).toJsCmd
  }

  /**
  * The companion object to FadeIn that provides an alternative factory
  */
  object FadeIn {
    /**
    * Fade In with the default duration and fadeTime provided by JsRules
    */
    def apply(id: String) = new FadeIn(id, JsRules.prefadeDuration, JsRules.fadeTime)
  }

  case class FadeIn(id: String, duration: TimeSpan, fadeTime: TimeSpan) extends JsCmd {
    def toJsCmd = (After(duration, JqJE.JqId(id) ~> (new JsRaw("fadeIn(" + fadeTime.millis + ")") with JsMember))).toJsCmd
  }

  object ModalDialog {
    def apply(html: NodeSeq) = new ModalDialog(html, Empty)

    def apply(html: NodeSeq, css: JsObj) = new ModalDialog(html, Full(css))
  }

  class ModalDialog(html: NodeSeq, css: Box[JsObj]) extends JsCmd {
    val toJsCmd = "jQuery.blockUI({ message: " + AltXML.toXML(Group(S.session.map(s =>
            s.fixHtml(s.processSurroundAndInclude("Modal Dialog", html))).openOr(html)), false, true, S.ieMode).encJs +
            (css.map(",  css: " + _.toJsCmd + " ").openOr("")) + "});"
  }

  case object Unblock extends JsCmd {
    def toJsCmd = "jQuery.unblockUI();"
  }

  /**
   * Use SetValueAndFocus from JsCmds
   */
  @deprecated
  case class SetValueAndFocus(id: String, value: String) extends JsCmd {
    def toJsCmd = "document.getElementById(" + id.encJs + ").value = " +
            value.encJs +
            "; document.getElementById(" + id.encJs + ").focus();"
  }

}

}
}
}
}