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

using concurrent
using syntax
using gfx
using fwt
using flux

**
** TextEditor provides a syntax color coded editor for
** working with text files.
**
class TextEditor : View
{

//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////

  override Widget? buildToolBar()
  {
    return EdgePane
    {
      top = InsetPane(4,4,5,4)
      {
        ToolBar
        {
          addCommand(frame.command(CommandId.save))
          addSep
          addCommand(frame.command(CommandId.cut))
          addCommand(frame.command(CommandId.copy))
          addCommand(frame.command(CommandId.paste))
          addSep
          addCommand(frame.command(CommandId.undo))
          addCommand(frame.command(CommandId.redo))
        },
      }
      bottom = find
    }
  }

  override Widget? buildStatusBar()
  {
    controller.updateCaretStatus()
    return GridPane
    {
      it.numCols = 2
      it.hgap = 10
      it.halignPane = Halign.right
      it.add(caretField)
      it.add(charsetField)
    }
  }

//////////////////////////////////////////////////////////////////////////
// Eventing
//////////////////////////////////////////////////////////////////////////

  override Void onActive()
  {
    frame.command(CommandId.find).enabled = true
    frame.command(CommandId.findNext).enabled = true
    frame.command(CommandId.findPrev).enabled = true
    frame.command(CommandId.replace).enabled = true
    frame.command(CommandId.goto).enabled = true

    // restore viewport and caret position
    caretOffset := Actor.locals["fluxText.caretOffset.$resource.uri"]
    topLine := Actor.locals["fluxText.topLine.$resource.uri"]
    if (caretOffset != null) richText.caretOffset = caretOffset
    if (topLine != null) richText.topLine = topLine
    richText.focus
  }

  override Void onInactive()
  {
    // save viewport and caret position
    Actor.locals["fluxText.caretOffset.$resource.uri"] = richText.caretOffset
    Actor.locals["fluxText.topLine.$resource.uri"] = richText.topLine
  }

  override Void onCommand(Str id, Event? event)
  {
    controller.trap("on${id.capitalize}", [event])
  }

  override Void onMarks(Mark[] marks)
  {
    doc.updateMarks(marks)
  }

  override Void onGotoMark(Mark mark)
  {
    controller.onGotoMark(mark)
  }

//////////////////////////////////////////////////////////////////////////
// Loading
//////////////////////////////////////////////////////////////////////////

  override Void onLoad()
  {
    // init
    file = resource->file

    // load the document into memory
    loadDoc
    charsetField.text = charset.toStr

    // create rich text widget
    richText = RichText { model = doc; border = false }

    richText.font = options.font
    richText.tabSpacing = options.tabSpacing

    // initialize controller
    controller = TextEditorController(this)
    controller.register

    // update ui
    find = FindBar(this)
    content = BorderPane
    {
      it.content  = richText
      it.border   = Border("1,0,1,1 $Desktop.sysNormShadow")
    }
  }

  internal Void loadDoc()
  {
    // read document into memory, if we fail with the
    // configured charset, then fallback to ISO 8859-1
    // which will always "work" since it is byte based
    lines := readAllLines
    if (lines == null)
    {
      charset = Charset.fromStr("ISO-8859-1")
      lines   = readAllLines
    }

    // save this time away to check on focus events
    fileTimeAtLoad = file.modified

    // figure out what syntax file to use
    // based on file extension and shebang
    rules = SyntaxRules.loadForFile(file, lines.first)
    if (rules == null) rules = SyntaxRules {}

    // load document
    doc = Doc(options, rules)
    doc.load(lines)
  }

  private Str[]? readAllLines()
  {
    in := file.in { it.charset = this.charset }
    try
      return in.readAllLines
    catch
      return null
    finally
      in.close
  }

//////////////////////////////////////////////////////////////////////////
// Saving
//////////////////////////////////////////////////////////////////////////

  override Void onSave()
  {
    out := file.out { it.charset = this.charset }
    try
      doc.save(out)
    finally
      out.close
    fileTimeAtLoad = file.modified
  }

//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////

  File? file
  TextEditorOptions options := TextEditorOptions.load
  Charset charset := options.charset
  SyntaxRules? rules
  RichText? richText
  Doc? doc
  internal TextEditorController? controller
  internal FindBar? find
  internal DateTime? fileTimeAtLoad
  internal Label caretField := Label()
  internal Label charsetField := Label()
}