/* 
 * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#pragma once

#ifndef SCI_NAMESPACE
#define SCI_NAMESPACE
#endif
#include "Scintilla.h"

#include <mforms/view.h>
#include <mforms/utilities.h>

/**
 * Provides a code editor with syntax highlighting for mforms.
 */

class TiXmlDocument;
class TiXmlElement;

namespace mforms {

  class CodeEditor;
  class Menu;
  class FindPanel;

  enum SyntaxHighlighterLanguage {
    LanguageNone,
    LanguageMySQL50,
    LanguageMySQL51,
    LanguageMySQL55,
    LanguageMySQL56,
    LanguageMySQL57,

    LanguageHtml,   // includes embedded xml, javascript, php, vb, python
    LanguagePython,
    LanguageCpp,    // Lexer for C++, C, Java, and JavaScript (which includes JSON).

    LanguageMySQL = LanguageMySQL56, // Always the latest (released) language.
  };

  /**
   * A number of flags used to specify visual appearance of an editor line.
   */
  enum LineMarkup {
    LineMarkupNone          = 0,      // No markup for the given line.
    LineMarkupStatement     = 1 << 0, // Marks a line as having a statement starting on it.
    LineMarkupError         = 1 << 1, // Line's background is drawn in red to mark an execution error.
    LineMarkupBreakpoint    = 1 << 2, // Line has a marker set for a break point.
    LineMarkupBreakpointHit = 1 << 3, // Line has a marker set for a break point which is currently hit.
    LineMarkupCurrent       = 1 << 4, // Current execution line.

    LineMarkupAll           = 0xFF,   // All markup, useful for remove_markup.
  };

  // A collection of markup, attached to the original line and going to be removed
  // or moved to a new line.
  typedef struct {
    int original_line;
    int new_line;
    LineMarkup markup;
  } LineMarkupChangeEntry;
  typedef std::vector<LineMarkupChangeEntry> LineMarkupChangeset;

#ifndef SWIG
  inline LineMarkup operator| (LineMarkup a, LineMarkup b)
  {
    return (LineMarkup) ((int) a | (int) b);
  }
#endif

  // Indicators for a portion of text. Can span more than a single line or only part of a line.
  enum RangeIndicator {
    RangeIndicatorNone   = 0,
    RangeIndicatorError  = 1 << 0, // Red squiggles under a range of text. 
  };

  enum CodeEditorFeature {
    FeatureNone               = 0,
    FeatureWrapText           = 1 << 0, // Enables word wrapping.
    FeatureGutter             = 1 << 1, // Show/Hide gutter.
    FeatureReadOnly           = 1 << 2,
    FeatureShowSpecial        = 1 << 3, // Show white spaces and line ends with special chars.
    FeatureUsePopup           = 1 << 4, // Use built-in context menu.
    FeatureConvertEolOnPaste  = 1 << 5, // Convert line endings to the current value in the editor
                                        // when pasting text.
    FeatureScrollOnResize     = 1 << 6, // Scroll caret into view if it would be hidden by a resize action.
    FeatureFolding            = 1 << 7, // Enable code folding.
    FeatureAutoIndent         = 1 << 8, // Auto indent the new line on pressing enter.

    FeatureAll               = 0xFFFF,
  };

#ifndef SWIG
  inline CodeEditorFeature operator| (CodeEditorFeature a, CodeEditorFeature b)
  {
    return (CodeEditorFeature) ((int) a | (int) b);
  }
#endif

  enum AutoCompletionEventType {
    AutoCompletionSelection,   // The user selected an entry in the auto completion list.
    AutoCompletionCancelled,   // Auto completion was cancelled.
    AutoCompletionCharDeleted, // A character was deleted while auto completion was active.
  };

  enum EndOfLineMode {
    EolCRLF = 0,
    EolCR   = 1,
    EolLF   = 2,   // Default
  };
  
  enum FindFlags {
    FindDefault =    0,
    FindMatchCase =  (1 << 0),
    FindWrapAround = (1 << 1),
    FindWholeWords = (1 << 2),
    FindRegex =      (1 << 3)
  };

#ifndef SWIG
  inline FindFlags operator | (FindFlags a, FindFlags b)
  {
    return (FindFlags) ((int) a | (int) b);
  }

  inline FindFlags& operator |= (FindFlags& a, FindFlags b)
  {
    a = (FindFlags)((int) a | (int) b);
    return a;
  }
#endif

#ifndef SWIG
  /**
   * Helper class to manage editor configuration files.
   */
class MFORMS_EXPORT CodeEditorConfig
{
private:
  std::vector<std::string> _languages;
  SyntaxHighlighterLanguage _used_language;

  std::map<std::string, std::string> _keywords;
  std::map<std::string, std::string> _properties;
  std::map<std::string, std::string> _settings;
  std::map<int, std::map<std::string, std::string> > _styles;

  TiXmlDocument *_document;
  TiXmlElement *_language_element;
protected:
  void parse_properties();
  void parse_settings();
  void parse_keywords();
  void parse_styles();

public:
  CodeEditorConfig(SyntaxHighlighterLanguage language);
  ~CodeEditorConfig();

  std::vector<std::string> get_languages() { return _languages; };

  // TODO: add setters when customization is required.
  std::map<std::string, std::string> get_keywords() { return _keywords; };
  std::map<std::string, std::string> get_properties() { return _properties; };
  std::map<std::string, std::string> get_settings() { return _settings; };
  std::map<int, std::map<std::string, std::string> > get_styles() { return _styles; };
};
#endif // !SWIG

#ifndef DOXYGEN_SHOULD_SKIP_THIS
#ifndef SWIG
  struct CodeEditorImplPtrs
  {
    bool (*create)(CodeEditor* self);
    sptr_t (*send_editor)(CodeEditor* self, unsigned int message, uptr_t wParam, sptr_t lParam);
    void (*set_status_text)(CodeEditor* self, const std::string &text);
  };
#endif
#endif

  class MFORMS_EXPORT CodeEditor : public View
  {
  public:
    CodeEditor(void *host = NULL);
    ~CodeEditor();

    /** Apply default settings for the editor. */
    void setup();

    /** Replaces the text in the editor. */
    void set_text(const char* text);
    void set_value(const std::string& text);

    /** Replaces the text in the editor but preserves top line, caret position and selection.
     *  This might not always work, especially when replacing large text by small text.
     */
    void set_text_keeping_state(const char* text);

    /** Appends the given number of chars to end of the document. Allows to add nulls too.
     *  The length is (as always) a byte count.
     */
    void append_text(const char* text, size_t length);

    /** Replaces the selected text in the editor by the new text. If no text is selected then 
     *  the new text is inserted at the caret position. */
    void replace_selected_text(const std::string& text);

    /** Returns a copy of the text which is currently in the editor. If selection_only is true only
     *  the current selection is returned. If there is no selection then the result is an empty
     *  string in that case.
     */
    const std::string get_text(bool selection_only);
    virtual std::string get_string_value() { return get_text(false); };

    /** Returns the text in the given range (inclusive endpoints), regardless of the selection state.
     *  The range is automatically adjusted if it lies outside the available total text range.
     */
    const std::string get_text_in_range(size_t start, size_t end);

    /** Returns a direct pointer to the text in the editor control (no copying takes place) and its length
     *  in bytes. The text can contain embedded nulls and should therefore not be handled like
     *  a null terminated string (even though it is actually null terminated).
     *  The call takes care to make the text a continuous block of characters terminated with an additional null.
     *  The returned pointer is valid until the next change in the editor happens, so use it only
     *  for short term tasks (e.g. error parsing, direct search etc.).
     *  Don't change the text in any way or the editor might get out of sync.
     */
    std::pair<const char*, size_t> get_text_ptr();

    /** Selects the text at the given range. If length is 0 it will just move the caret.
     * NOTE: Scintilla uses bytes everywhere when a position or length is set or read. So be very
     *       careful when doing char maths (we use utf-8 with a variable code length per character).
     */
    void set_selection(size_t start, size_t length);

    /** Gets the current selection range. */
    void get_selection(size_t &start, size_t &length);

    /** Removes the current selection without moving the caret. */
    void clear_selection();

    /** Gets the byte range for the given line. Returns false if the line number is invalid */
    bool get_range_of_line(ssize_t line, ssize_t &start, ssize_t &end);
    
    /** Sets the language for the syntax highlighter. */
    void set_language(SyntaxHighlighterLanguage language);

    /** Adds the given markup to a line if not yet there. Does not touch other markup. */
    void show_markup(LineMarkup markup, size_t line);

    /** Removes the given markup from that line, without affecting other markup (except for LineMarkupAll).
     *  If markup is LineMarkupAll then all markers are removed for the given line.
     *  If line is < 0 then all marker are removed from all lines.
     *  However, it is not possible to remove a specific marker from all lines.
     */
    void remove_markup(LineMarkup markup, ssize_t line);

    /**
     * Determines if the given line contains the given markup.
     * Returns true if at least one of the given markup types was found.
     */
    bool has_markup(LineMarkup markup, size_t line);

    /** Adds the given indicator styling to a range of characters. */
    void show_indicator(RangeIndicator indicator, size_t start, size_t length);

    /** Returns the indicator styling at the given position (if any). */
    RangeIndicator indicator_at(size_t position);

    /** Removes the given indicator styling from the given range. */
    void remove_indicator(RangeIndicator indicator, size_t start, size_t length);

    /** Returns the number of lines currently in the editor. */
    size_t line_count();

    /** The total length of the text in the editor in bytes. */
    size_t text_length();

    /** Returns the character position of the given line. */
    size_t position_from_line(size_t line_number);

    /** Returns the line number from the given character position. */
    size_t line_from_position(size_t position);

    void set_font(const std::string &fontDescription); // e.g. "Trebuchet MS bold 9"
    
    /** Enables or disables different features in the editor which have a yes/no behavior. */
    void set_features(CodeEditorFeature features, bool flag);

    /** Toggles the given feature(s) to their opposite state. */
    void toggle_features(CodeEditorFeature features);

    void set_read_only(bool flag);

    void reset_undo_stack();

    /** Resets the editor's dirty state or queries it. */
    void reset_dirty();
    bool is_dirty();

    /** Retrieves or sets the position of the caret in the editor, specified as byte position. */
    size_t get_caret_pos();
    void set_caret_pos(size_t position);
    
    /** Retrieves the line and column (both zero-based) for a given byte position. */
    void get_line_column_pos(size_t position, size_t &line, size_t &column);

    /** Standard edit functions used from menus. */
    bool can_undo();
    void undo();
    bool can_redo();
    void redo();
    bool can_cut();
    void cut();
    bool can_copy();
    void copy();
    bool can_paste();
    void paste();
    bool can_delete();
    void do_delete();
    void select_all();

    /** Sets the given text in the status field of the editor. Not all platforms support this, though. */
    void set_status_text(const std::string& text);
    
    // ----- Find and replace
    void show_find_panel(bool replace);
    void hide_find_panel();
    FindPanel* get_find_panel() { return _find_panel; };

    /** Used to set a callback which is called to set up the layout for the find panel and
     *  shows/hides it as requested. This allows to decouple platform specific needs for embedding
     *  the find panel in various parts of the application (even non-mforms).
     */
    void set_show_find_panel_callback(boost::function<void (CodeEditor*, bool)> callback);

    /** Searches for the given text according to the parameters and selects the first occurrence.
     *  Returns true if something was found, false otherwise. */
    bool find_and_highlight_text(const std::string& search_text, FindFlags flags,
      bool scroll_to, bool backwards);

    /** Searches for the given text according to the parameters and replaces it by the text.
     *  Returns the number of replaced text occurrences. */
    size_t find_and_replace_text(const std::string& search_text, const std::string& new_text,
      FindFlags flags, bool do_all);

    /** Searches for the next placeholder char combination and selects it when found. The text is
     * also scrolled into view. */
    void jump_to_next_placeholder();
    
    //----- Auto completion -----

    /** Shows the auto completion list at the current cursor position.
     *  
     *  @param chars_entered The number chars already entered for the word which is being completed.
     *  @param entries A list of strings to show in the auto completion window. The variant with the
     *                 int part additionally takes an image id for each entry. Images must be registered
     *                 first with auto_completion_register_images. Use -1 as id when no image is needed.
     *                 The list should be sorted to make matching working properly.
     */
    void auto_completion_show(size_t chars_entered, const std::vector<std::pair<int, std::string> >& entries);
    void auto_completion_show(size_t chars_entered, const std::vector<std::string>& entries);

    /** Can be used to cancel auto completion while it is in progress (i.e. during handling an
     * auto completion event. Has no effect otherwise. */
    void auto_completion_cancel();

    /** Used to set a few simple options for auto completion.
     * 
     * @param ignore_case If true matching of characters to list members is not case sensitive.
     * @param choose_single If true and only one entry is in the auto completion list then this entry
     *                      is automatically used without showing the list.
     * @param auto_hide If true the list will automatically be hidden when there's no matching entry left.
     * @param drop_rest_of_word When an item is selected, any word characters following the caret are
     *                          first erased if this parameter is set true.
     * @param cancel_at_start If true the list is hidden when the caret moves to position it was when
     *                        auto completion started.
     */
    void auto_completion_options(bool ignore_case, bool choose_single, bool auto_hide,
      bool drop_rest_of_word, bool cancel_at_start);

    /** Configures the maximum size of the auto completion list. If not set then the largest entry
     *  in the list is used to determine the total width and the height is set to show 5 entries.
     *  
     *  @param width The number of characters to show at most. Longer entries will be shorted using ellipses.
     *  @param  height The number of entries (rows) to show. If there are more entries a vertical scrollbar is shown.
     */
    void auto_completion_max_size(int width, int height);

    /** Used to load images (png or xpm type) into the editor and associate them with image ids, which can
     *  be used to display them together with the text in the auto completion list.
     *  
     *  @param images A list of image file names that get loaded.
     */
    void auto_completion_register_images(const std::vector<std::pair<int, std::string> >& images);

    /** Returns true if auto completion is currently active (i.e. the list is visible). */
    bool auto_completion_active();

    /** The characters in the given string automatically cancel auto completion. By default no stops are defined. */
    void auto_completion_stops(const std::string& stops);

    /** If a fill-up character is typed while auto completion is active, the currently selected entry
     *  item in the list is added into the document, then the fill-up character is added. By default
     *  there is no fill-up character defined. */
    void auto_completion_fillups(const std::string& fillups);

    /** Show the editor's calltip window close to the given position or hide it. */
    void show_calltip(bool show, size_t position, const std::string& value);

    /** Sets the EOL mode used by the editor. If @convert is true all lines in the document are converted
     *  to use the new line ending.
     */
    void set_eol_mode(mforms::EndOfLineMode mode, bool convert = false);

    /** Sets a context menu to be attached to the editor, to be shown on right click.
     
     Note: Ownership of the context menu remains with the caller and it will not be freed
     when this object is deleted. */
    void set_context_menu(Menu *menu) { _context_menu = menu; };
    
    /** Returns the context menu object attached to the editor. */
    Menu *get_context_menu() { return _context_menu; }
    
    /** Returns the host which is controlling this editor instance (if any). */
    void *get_host() { return _host; }

    /** Direct access to the editor backend, for everything not covered here. */
    sptr_t send_editor(unsigned int message, uptr_t wParam, sptr_t lParam);
    
#ifndef SWIG
    /** Signal emitted when content is edited
     *  Parameters are:
     *    The (byte) position in the text where the change happened.
     *    The length of the change (in bytes).
     *    The number of lines which have been added (if positive) or removed (if negative).
     *    True if text was inserted.
     */
    boost::signals2::signal<void (int, int, int, bool)>* signal_changed() { return &_change_event; }

    /** Signal emitted when the user clicks on the gutter.
     *  Parameters are:
     *    The margin (part of the gutter) the click occurred (0 = line numbers, 1 = markers, 2 = folding).
     *    The line in which this happened.
     *    The modifier keys that were pressed.
     */
    boost::signals2::signal<void (int, int, mforms::ModifierKey)>* signal_gutter_clicked() { return &_gutter_clicked_event; }

    /** Event sent when auto completion notifications from Scintilla come in.
     *  Parameters are:
     *    The type of auto completion that happened. Only used with AutoCompletionSelection.
     *    The start position of the word being completed. Only used with AutoCompletionSelection.
     *    The text of the selection.
     */
    boost::signals2::signal<void (AutoCompletionEventType, int, const std::string&)>* signal_auto_completion() { return &_auto_completion_event; };

    /** Signal emitted when the user keeps the mouse in one position for the dwell period or when
     *  when new events occur (text insertion, mouse move etc.) after dwelling started.
     *  Parameters are:
     *    Flag that tells if we are start dwelling or stop it.
     *    The position closest to the mouse pointer.
     *    x and y client coordinates where the mouse lingered.
     */
    boost::signals2::signal<void (bool, size_t, int, int)>* signal_dwell() { return &_dwell_event; }

    /** Signal emitted when the user typed an ordinary text character (as opposed to a command character).
     *  It can be used e.g. to trigger auto completion.
     *  Parameter is:
     *    The character code.
     */
    boost::signals2::signal<void (int)>* signal_char_added() { return &_char_added_event; }

    /** Signal emitted when the Scintilla backend removes a set marker (e.g. on editing, pasting, manual marker setting).
     *  Parameters are:
     *    A vector of line + markup pairs.
     *    A flag telling if those markers where deleted or only updated (moved).
     */
    boost::signals2::signal<void(const LineMarkupChangeset &changeset, bool deleted)>* signal_marker_changed() { return &_marker_changed_event; }

    /** Signal emitted when the control loses input focus.
     */
    boost::signals2::signal<void ()>* signal_lost_focus() { return &_signal_lost_focus; }
#ifndef DOXYGEN_SHOULD_SKIP_THIS
    /** Called by the platform code forwarding us all scintilla notifications, so we can act on them. */
    void on_notify(Scintilla::SCNotification* notification);

    /** Called by the platform code forwarding us all scintilla commands, so we can act on them. */
    void on_command(int command);
    void lost_focus();

    virtual void resize();

#endif
#endif
  protected:
    CodeEditorImplPtrs* _code_editor_impl;
    Menu *_context_menu;
    FindPanel* _find_panel;
    std::map<int, void*> _images; // Registered RGBA images.
    void *_host;
    bool _scroll_on_resize;
    bool _auto_indent;

    void setup_marker(int marker, const std::string& name);
    void check_markers_removed(int position, int length);
    void check_markers_moved(int position, int lines_added);

    void load_configuration(SyntaxHighlighterLanguage language);

    boost::signals2::signal<void (int, int, int, bool)> _change_event;
    boost::signals2::signal<void (int, int, mforms::ModifierKey)> _gutter_clicked_event;
    boost::signals2::signal<void (AutoCompletionEventType, int, const std::string&)> _auto_completion_event;
    boost::signals2::signal<void (bool, size_t, int, int)> _dwell_event;
    boost::signals2::signal<void (int)> _char_added_event;
    boost::signals2::signal<void ()> _signal_lost_focus;
    boost::signals2::signal<void(const LineMarkupChangeset &changeset, bool deleted)> _marker_changed_event;

    boost::function<void (CodeEditor*, bool)> _show_find_panel;
  };
};
