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

using fwt
using gfx

**
** FileUploader enables file uploading from a browser.
**
@Js
class FileUploader : Pane
{
  ** Return a Dialog to wrap given FileUploader instance.
  static Dialog dialog(Window w, FileUploader f)
  {
    loc := "$<upload=Upload>"
    dlg := WebDialog(w)
    {
      title = loc
      body  = f
      insetsBody = Insets(10)
    }
    Command? upload
    upload = Command.make(loc, null) |e|
    {
      upload.enabled = false
      f.onComplete.add
      {
        dlg.commands = [Dialog.ok]
        dlg.buildContent
        dlg.relayout
      }
      f.upload
    }
    upload.enabled = false
    f._onFilesChanged.add |e| { upload.enabled = e.index > 0 }
    dlg.commands = [upload, Dialog.cancel]
    return dlg
  }

  ** It-block constructor.
  new make(|This|? f := null)
  {
    if (f != null) f(this)
    top = ContentPane()
    list = multi
      ? WebScrollPane { bg=Color.white }
      : BorderPane { bg=Color.white; border=Border("#9f9f9f") }
    content = EdgePane
    {
      // minh prevents content for jumping when icon changes
      it.top = ConstraintPane { minh=32; this.top, }
      center = ConstraintPane
      {
        prefh := multi ? (5*36) : 36
        minw=400; maxw=400
        minh=prefh; maxh=prefh
        list,
      }
    }
    add(content)
    reset
  }

  ** Allow multiple file uploading.
  const Bool multi := false

  ** Target URI to upload files. Each file is uploaded on a
  ** discrete POST request.  The original name of the file is
  ** contained in the 'FileUpload-filename' request header.
  const Uri uri

  ** If 'true' the POST content is formatted as "multipart/form-data".
  ** If 'false' the raw file content is posted.
  const Bool useMultiPart := false

  ** Additional HTTP headers to POST along with file content.
  const Str:Str headers := [:]

  ** EventListener invoked when all uploads have completed.
  once EventListeners onComplete() { EventListeners() }

  ** Invoke upload process for currently selected files.
  Void upload()
  {
    working = true
    top.content = Label
    {
      image = Image(`fan://webfwt/res/img/throbber.gif`)
      text  = "$<uploading=Uploading...>"
    }
    onSubmit
    relayout
  }

  ** Reset this uploader, clearing all completed uploads
  ** or selected files.
  Void reset()
  {
    if (working) throw Err("Upload in progress")
    top.content = GridPane
    {
      numCols = 2
      MiniButton { text="$<chooseFile=Choose File>"; onAction.add { onChoose }},
      Label { text="$<orDragFilesHere=or drag files here>" },
    }
    onClear
    relayout
  }

  ** Callback when all file uploads are complete.
  private Void onUploadComplete(FileUpload[] files)
  {
    // map files into name:response
    map := Str:Str[:]
    files.each |f| { map[f.name] = f.response }

    // update interface and notify listeners
    working = false
    top.content = Label
    {
      image = Image(`fan://icons/x16/check.png`)
      text  = "$<uploadComplete=Upload complete>"
    }
    relayout
    onComplete.fire(Event { id=EventId.action; widget=this; data=map.ro })
  }

  override Size prefSize(Hints hints := Hints.defVal)
  {
    cp := content.prefSize
    return Size(cp.w+12, cp.h+12)
  }

  override Void onLayout()
  {
    content.bounds = Rect(6, 6, size.w-12, size.h-12)
  }

  private native Void onChoose()
  private native Void onRemove(Int index)
  private native Void onClear()
  private native Void onSubmit()

  private Void onFilesChanged(FileUpload[] files)
  {
    vpane := VPane()
    files.each |file,i|
    {
      Widget? status
      if (!file.active)
        status = MiniButton { text="$<remove=Remove>"; onAction.add { onRemove(i) }}
      else if (file.inProgress)
        status = ProgressBar { val=file.progress }
      else
        status = Label { text=file.status; fg=Color("#777") }

      vpane.add(FileUploadRow([
        WebLabel { text=file.name; softClip=true },
        status,
      ], multi && i > 0))
    }
    list.content = vpane
    list.relayout

    // internal hook for dialog
    _onFilesChanged.fire(Event { index=files.size })
  }

  // internal hook for dialog
  internal once EventListeners _onFilesChanged() { EventListeners() }

  private Widget content
  private ContentPane top
  private ContentPane list
  private Bool working := false
}

**************************************************************************
** FileUpload
**************************************************************************
@Js
internal class FileUpload
{
  Str? name               // file name
  Obj? file               // file object
  Bool active   := false  // is upload active
  Bool waiting  := true   // is upload waiting for connection
  Bool complete := false  // is upload complete
  Int progress := 0       // if uploading, cur progress as 0..100%
  Str response := ""      // response text upon completion

  Str status()
  {
    if (!active)  return ""
    if (waiting)  return "$<waiting=Waiting>"
    if (complete) return "$<complete=Complete>"
    return "$progress%"
  }

  Bool inProgress() { active && !waiting && !complete }
}

**************************************************************************
** FileUploadRow
**************************************************************************
@Js
internal class FileUploadRow : Pane
{
  new make(Widget[] kids, Bool sep)
  {
    addAll(kids)
    if (sep) add(BorderPane { border=Border("1,0,0,0 #ccc") })
  }

  override Size prefSize(Hints hints := Hints.defVal) { Size(400, 36) }

  override Void onLayout()
  {
    w := size.w - 12
    h := size.h - 12

    b := children[1]
    bp := b.prefSize
    bx := w - bp.w
    by := (h - bp.h) / 2
    b.bounds = Rect(6+bx, 6+by, bp.w, bp.h)

    a  := children.first
    ap := a.prefSize
    ay := (h - ap.h) / 2
    aw := w - bp.w - 12
    a.bounds = Rect(6, 6+ay, aw, ap.h)

    if (children.size == 3) children.last.bounds = Rect(0, 0, size.w, 1)
  }
}