/*
 * Copyright 2007-2009 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.widgets.flot

import scala.xml.{NodeSeq, Node, PCData, Text, Unparsed}
import _root_.net.liftweb.http.{LiftRules}
import _root_.net.liftweb.http.js._
import JsCmds._
import JE._
import _root_.net.liftweb.util._
import Helpers._

/**
 * renders a flot graph using http://code.google.com/p/flot/ jQuery widget
 * <br />
 * See the sites/flotDemo webapp for examples.
 */

object Flot
{
  /**
   * register the resources with lift (typically in boot)
   */

  def init() {
    import net.liftweb.http.ResourceServer

    ResourceServer.allow({
        case "flot" :: "jquery.flot.js" :: Nil => true
        case "flot" :: "excanvas.pack.js" :: Nil => true
      })
  }

  def script(xml: NodeSeq): JsCmd =
    (xml \ "script").map(x => JsRaw(x.text).cmd).foldLeft(Noop)(_ & _)

  /**
   * render a flot graph
   * <p>
   * a comet actor should use this version
   */

  def render(idPlaceholder: String,
             datas: List[FlotSerie],
             options: FlotOptions,
             script: JsCmd,
             caps: FlotCapability*
  ): NodeSeq =
  {
    val ieExcanvasPackJs = Unparsed("<!--[if IE]><script language=\"javascript\" type=\"text/javascript\" src=\"" +
                                    net.liftweb.http.S.contextPath + "/" +
                                    LiftRules.resourceServerPath + "/flot/excanvas.pack.js\"></script><![endif]-->")

    <head>
      <script type="text/javascript" src={"/" + LiftRules.resourceServerPath + "/flot/jquery.flot.js"}></script>
      {ieExcanvasPackJs}
      {
        Script(_renderJs(idPlaceholder, datas, options, script, caps :_*))
      }
    </head>
  }
  /*
   *
   */

  def renderCapability (fRender: FlotCapability => JsCmd, caps: FlotCapability *): JsCmd =
  caps.foldLeft(Noop)((js, cap) => js & fRender(cap))


  /*
   * can be used to generate AJAX response
   */

  def renderJs (
    idPlaceholder : String,
      datas : List [FlotSerie],
      options : FlotOptions,
      script: JsCmd,
      caps : FlotCapability *): JsCmd =
  datas match {
    case Nil => renderFlotHide(idPlaceholder, caps: _*)

    case _ => renderVars(idPlaceholder, datas, options) &
      renderFlotShow(idPlaceholder, datas, options, script, caps :_*)
  }

  //

  def renderFlotHide (idPlaceholder: String, caps: FlotCapability *): JsCmd =
  JsHideId(idPlaceholder) &
  renderCapability (c => c.renderHide(), caps :_*)


  // part that belongs to jQuery "document ready" function

  def renderFlotShow (
    idPlaceholder: String,
      datas: List [FlotSerie],
      options: FlotOptions,
      script: JsCmd,
      caps: FlotCapability *): JsCmd = {

    val main = FlotInfo (idPlaceholder, datas, options)

    JsShowId(idPlaceholder) &
    renderCapability (c => c.renderShow (), caps :_*) &
    JsRaw(
      "var plot_" + idPlaceholder +
      " = jQuery.plot(jQuery(" + ("#"+idPlaceholder).encJs +
      "), datas_" + idPlaceholder +
      ", options_" + idPlaceholder + ")") &
    renderCapability (c => c.render (main), caps :_*) &
    script
  }

  // generate Javascript inside "document ready" event

  private def _renderJs (
    idPlaceholder : String,
      datas : List [FlotSerie],
      options : FlotOptions,
      script: JsCmd,
      caps : FlotCapability*): JsCmd = {
    renderVars (idPlaceholder, datas, options) &
    OnLoad(
      (datas match {
          case Nil => renderFlotHide(idPlaceholder, caps : _*)
          case _ => renderFlotShow(idPlaceholder, datas, options, script,
                                   caps : _*)
        }))


  }

  /*
   private def renderJqueryScript (jqueryScript: Seq[Node]) : JsCmd = {
   jqueryScript.foldLeft ("") ( (sz,node) => {
   sz + (node match {
   case net.liftweb.util.PCData (_s) => _s
   case _ => node.toString
   })
   })
   }

   //

   val initFlot = "jQuery(function () {"
   val endFlot = "});"
   */
  /**
   * render a data value:<br/>
   * [2, 10]
   */
  def renderOneValue (one: (Double, Double)) : JsExp =
  one match {
    case (Math.NaN_DOUBLE, _) => JsNull
    case (_, Math.NaN_DOUBLE) => JsNull
    case (a, b) => JsArray(a, b)
  }


  /**
   * render serie of data:<br/>
   * [2, 10], [5, 12], [11, 2]
   */
  def renderValues(values: List[(Double, Double)]): JsExp =
  JsArray(values.map(renderOneValue) :_*)


  /**
   *
   */

  def renderDataSerie(idPlaceholder: String)(data: (FlotSerie, Int)): JsCmd =
  JsCrVar("data_"+idPlaceholder+"_"+(data._2 + 1), renderValues(data._1.data))

  /*
   * render all variables that can be modified via Javascript after first page load (for example using Ajax or comet)
   */

  def renderVars (idPlaceholder : String,
                  datas: List[FlotSerie],
                  options: FlotOptions): JsCmd =
  datas match {
    case Nil => Noop

    case _ =>
      datas.zipWithIndex.map(renderDataSerie(idPlaceholder)).
      reduceLeft(_ & _) &
      JsCrVar("datas_"+idPlaceholder, renderSeries(datas, idPlaceholder)) &
      JsCrVar("options_"+idPlaceholder, options.asJsObj)
  }



  /**
   * render one serie:<br />
   * <br />
   * <code>
   * (
   *   label: "<name_label>"
   *   lines: { show: true, fill: true }
   *   bars: { show: true }
   *   points: { show: true }
   *   data: data_[ph]_[x] where ph is the placeholder id and {x} serie's the id
   * )
   * </code>
   */

  def renderOneSerie(data: FlotSerie, idPlaceholder: String, idSerie: Int): JsObj = {
    val info: List[Box[(String, JsExp)]] =
    List(data.label.map(v => ("label", v)),
         data.lines.map(v => ("lines", v.asJsObj)),
         data.points.map(v => ("points", v.asJsObj)),
         data.bars.map(v => ("bars", v.asJsObj)),
         data.color.map {
        case Left(c) => ("color", c)
        case Right(c) => ("color", c)
      },
         data.shadowSize.map(s => ("shadowSize", s)),
         Full(("data", JsVar("data_"+idPlaceholder + "_" + idSerie))))

    JsObj(info.flatten(_.toList) :_*)
  }

  /**
   * render all series: <br />
   * <br />
   * ( <br />
   *   label: "<name_label_1>" <br />
   *   lines:  ... <br />
   *   data: [[2, 10], [5, 12], [11, 2]] <br />
   * ), <br />
   * (<br />
   *   label: "<name_label2>"<br />
   *   data: [[2, 14], [6, 4], [11, 17]]<br />
   * )<br />
   *
   */
  def renderSeries(datas: List[FlotSerie], idPlaceholder: String): JsArray =
  JsArray(datas.zipWithIndex.map{
      case (d, idx) => renderOneSerie(d, idPlaceholder, idx + 1)
    } :_*)


  //


  //
  // min: 0, max: 10, tickDecimals: 0
  // mode: "time",
  // minTickSize: [1, "month"],    // TODO
  //
  /*
   def renderAxisOptions (options: FlotAxisOptions): JsObj = {
   val info: List[Box[(String, JsExp)]] =
   List(options.min.map(v => ("min", v)),
   options.max.map(v => ("max", v)),
   options.tickDecimals.map(v => ("tickDecimals", v)),
   options.ticks match {
   case Nil => Empty
   case x :: Nil => Full(("ticks", x))
   case xs => Full(("ticks", JsArray(xs.map(d => Num(d)) :_*)))
   },
   options.mode.map(v => ("mode", v))
   )

   JsObj(info.flatten(_.toList) :_*)
   }*/

  //
  //    xaxis: { tickDecimals: 0 },
  //    yaxis: { min: 0, max: 10 },
  //
  /*
   def renderAxis (axis : String, options : FlotAxisOptions) : String = {
   axis + "axis: {" + renderAxisOptions (options) + "}"
   }
   */

  //
  //
  //


  //
  //
  //




  //
  // {
  //    lines: { show: true},
  //    points: { show: true}
  //    xaxis: { tickDecimals: 0 },
  //    yaxis: { min: 0, max: 10 },
  //    selection: { mode: "x" }
  //    legend: { noColumns: 2 },
  // }
  //
  /*
   def renderOptions(options: FlotOptions): JsExp = {
   var first = true

   def endOfLine () = {
   val ret = if (! first) ",\n      " else "      "
   first = false
   ret
   }

   val set_lines = options.lines match {
   case None => ""
   case Some (_lines) => {first=false; "lines: {" + renderLines (_lines) + "}"}
   }

   val set_points = options.points match {
   case None => ""
   case Some (_points) => {endOfLine + "points: {" + renderPoints (_points) + "}"}
   }

   val set_xaxis = options.xaxis match {
   case None => ""
   case Some (options) => {endOfLine + renderAxis ("x", options)}
   }

   val set_yaxis = options.yaxis match {
   case None => ""
   case Some (options) => {endOfLine + renderAxis ("y", options)}
   }

   val set_selection = options.modeSelection match {
   case None => ""
   case Some (mode) => {endOfLine + "selection: { mode: '" + mode + "'}"}
   }

   val set_legend = options.legend match {
   case None => ""
   case Some (_legend) => {endOfLine + "legend: {" + renderLegend (_legend) +  "}"}
   }

   val set_shadowSize = options.shadowSize match {
   case None => ""
   case Some (_shadowSize) => {endOfLine + "shadowSize: " + _shadowSize}
   }

   val set_grid = options.grid match {
   case None => ""
   case Some (_grid) => {endOfLine + "grid: {" + renderGrid (_grid) + "}"}
   }

   if (! first)
   {
   "{\n" +
   set_lines +
   set_points  +
   set_xaxis +
   set_yaxis +
   set_selection +
   set_legend +
   set_shadowSize +
   set_grid +
   "  }"
   }
   else
   "{}"
   }

   def renderId (id : String) : String = {
   "'#" + id + "'"
   }
   */
}