# Copyright (c) 2023 M.J.N. Corino, The Netherlands
#
# This software is released under the MIT license.

###
# wxRuby3 Scintilla based sampler editor control
###

module WxRuby
  
  class SampleEditorCtrl < Wx::STC::StyledTextCtrl

    STYLE_DEFAULT = 32
    SCE_RB_DEFAULT=0
    SCE_RB_ERROR=1
    SCE_RB_COMMENTLINE=2
    SCE_RB_POD=3
    SCE_RB_NUMBER=4
    SCE_RB_WORD=5
    SCE_RB_STRING=6
    SCE_RB_CHARACTER=7
    SCE_RB_CLASSNAME=8
    SCE_RB_DEFNAME=9
    SCE_RB_OPERATOR=10
    SCE_RB_IDENTIFIER=11
    SCE_RB_REGEX=12
    SCE_RB_GLOBAL=13
    SCE_RB_SYMBOL=14
    SCE_RB_MODULE_NAME=15
    SCE_RB_INSTANCE_VAR=16
    SCE_RB_CLASS_VAR=17
    SCE_RB_BACKTICKS=18
    SCE_RB_DATASECTION=19
    SCE_RB_HERE_DELIM=20
    SCE_RB_HERE_Q=21
    SCE_RB_HERE_QQ=22
    SCE_RB_HERE_QX=23
    SCE_RB_STRING_Q=24
    SCE_RB_STRING_QQ=25
    SCE_RB_STRING_QX=26
    SCE_RB_STRING_QR=27
    SCE_RB_STRING_QW=28
    SCE_RB_WORD_DEMOTED=29
    SCE_RB_STDIN=30
    SCE_RB_STDOUT=31
    SCE_RB_STDERR=40
    SCE_RB_STRING_W=41
    SCE_RB_STRING_I=42
    SCE_RB_STRING_QI=43
    SCE_RB_STRING_QS=44
    SCE_RB_UPPER_BOUND=45

    IND_SEARCH_MATCH = 8
    
    def initialize(owner, *args)
      super(*args)
      @owner = owner

      # last search area matched
      @search_indicator = nil
      @find_replace = false

      font = Wx::Font.new(10, Wx::FONTFAMILY_TELETYPE, Wx::FONTSTYLE_NORMAL, Wx::FONTWEIGHT_NORMAL)
      style_set_font(Wx::STC::STC_STYLE_DEFAULT, font)

      @ws_visible = false
      @eol_visible = false
      set_edge_mode(Wx::STC::STC_EDGE_LINE)

      line_num_margin = text_width(Wx::STC::STC_STYLE_LINENUMBER, "_99999")
      set_margin_width(0, line_num_margin)

      set_tab_width(2)
      set_use_tabs(false)
      set_tab_indents(true)
      set_back_space_un_indents(true)
      set_indent(2)
      set_edge_column(80)

      indicator_set_style(IND_SEARCH_MATCH, Wx::STC::STC_INDIC_BOX)

      set_lexer(Wx::STC::STC_LEX_RUBY)
      default_theme
      set_key_words(0, "begin break elsif module retry unless end case next return until class ensure nil self when def false not super while alias defined? for or then yield and do if redo true else in rescue undef")

      set_property("fold","1")
      set_property("fold.compact", "0")
      set_property("fold.comment", "1")
      set_property("fold.preprocessor", "1")

      set_margin_width(1, 0)
      set_margin_type(1, Wx::STC::STC_MARGIN_SYMBOL)
      set_margin_mask(1, Wx::STC::STC_MASK_FOLDERS)
      set_margin_width(1, 20)

      marker_define(Wx::STC::STC_MARKNUM_FOLDER, Wx::STC::STC_MARK_BOXPLUS)
      marker_define(Wx::STC::STC_MARKNUM_FOLDEROPEN, Wx::STC::STC_MARK_BOXMINUS)
      marker_define(Wx::STC::STC_MARKNUM_FOLDEREND, Wx::STC::STC_MARK_BOXPLUSCONNECTED)
      marker_define(Wx::STC::STC_MARKNUM_FOLDERMIDTAIL, Wx::STC::STC_MARK_TCORNER)
      marker_define(Wx::STC::STC_MARKNUM_FOLDEROPENMID, Wx::STC::STC_MARK_BOXMINUSCONNECTED)
      marker_define(Wx::STC::STC_MARKNUM_FOLDERSUB, Wx::STC::STC_MARK_VLINE)
      marker_define(Wx::STC::STC_MARKNUM_FOLDERTAIL, Wx::STC::STC_MARK_LCORNER)
      set_fold_flags(16)

      set_margin_sensitive(1,1)

      self.undo_collection = true

      evt_stc_charadded(self.id, :on_char_added)
      evt_stc_marginclick(self.id, :on_margin_click)
      evt_stc_updateui(self.id, :on_update_ui)
      evt_stc_change(self.id, :on_change)

      self.accelerator_table = Wx::AcceleratorTable[[ Wx::MOD_SHIFT|Wx::MOD_CONTROL, 'I', Wx::ID_INFO]]
      evt_menu(Wx::ID_INFO) { Wx.message_box("Style = #{get_style_at(current_pos)}") }
    end

    def default_theme
      c_maroon = Wx::Colour.new('Maroon')
      style_set_foreground(Wx::STC::STC_STYLE_DEFAULT, Wx::BLACK)
      style_set_background(Wx::STC::STC_STYLE_DEFAULT, Wx::WHITE)
      set_caret_foreground(Wx::BLACK)
      style_clear_all
      style_set_foreground(Wx::STC::STC_STYLE_LINENUMBER, Wx::Colour.new('Dark Grey'))
      style_set_background(Wx::STC::STC_STYLE_LINENUMBER, Wx::WHITE);
      style_set_foreground(Wx::STC::STC_STYLE_INDENTGUIDE, Wx::LIGHT_GREY);
      set_whitespace_background(false, Wx::Colour.new('Dark Slate Grey'))
      style_set_foreground(SCE_RB_COMMENTLINE, Wx::Colour.new('Dark Green'))
      style_set_bold(SCE_RB_COMMENTLINE, true)
      style_set_foreground(SCE_RB_WORD, Wx::BLACK)
      style_set_bold(SCE_RB_WORD, true)
      style_set_foreground(SCE_RB_OPERATOR, Wx::Colour.new('Dark Olive Green'))
      style_set_bold(SCE_RB_OPERATOR, true)
      style_set_foreground(SCE_RB_POD, Wx::Colour.new('Grey'))
      style_set_foreground(SCE_RB_NUMBER, Wx::BLUE)
      style_set_foreground(SCE_RB_STRING, c_maroon)
      style_set_foreground(SCE_RB_CHARACTER, Wx::RED)
      style_set_foreground(SCE_RB_SYMBOL, Wx::Colour.new('Navy'))
      style_set_bold(SCE_RB_SYMBOL, true)
      if Wx::WXWIDGETS_VERSION >= '3.3.0'
        style_set_foreground(SCE_RB_HERE_DELIM, Wx::BLACK)
        style_set_bold(SCE_RB_HERE_DELIM, true)
        style_set_foreground(SCE_RB_HERE_Q, c_maroon)
        style_set_foreground(SCE_RB_HERE_QQ, c_maroon)
        style_set_foreground(SCE_RB_HERE_QX, c_maroon)
        style_set_foreground(SCE_RB_STRING_Q, c_maroon)
        style_set_foreground(SCE_RB_STRING_QQ, c_maroon)
        style_set_foreground(SCE_RB_STRING_QX, c_maroon)
        style_set_foreground(SCE_RB_STRING_QR, c_maroon)
        style_set_foreground(SCE_RB_STRING_QW, c_maroon)
      end
      bg = Wx::Colour.new('Light Grey')
      fg = Wx::Colour.new('Cadet Blue')
      set_fold_margin_colour(true, bg)
      set_fold_margin_hi_colour(true, bg)
      marker_set_foreground(Wx::STC::STC_MARKNUM_FOLDER, fg)
      marker_set_foreground(Wx::STC::STC_MARKNUM_FOLDEROPEN, fg)
      marker_set_foreground(Wx::STC::STC_MARKNUM_FOLDEREND, fg)
      marker_set_foreground(Wx::STC::STC_MARKNUM_FOLDEROPENMID, fg)
    end

    def dark_theme
      bg = Wx::Colour.new('Dark Slate Grey')
      c_str = Wx::Colour.new('Lime Green')
      style_set_background(Wx::STC::STC_STYLE_DEFAULT, bg)
      style_set_foreground(Wx::STC::STC_STYLE_DEFAULT, Wx::WHITE)
      set_caret_foreground(Wx::WHITE)
      style_clear_all
      style_set_background(Wx::STC::STC_STYLE_LINENUMBER, bg)
      style_set_foreground(Wx::STC::STC_STYLE_LINENUMBER, Wx::WHITE)
      style_set_foreground(Wx::STC::STC_STYLE_INDENTGUIDE, bg);
      set_whitespace_background(true, bg)
      style_set_eol_filled(SCE_RB_DEFAULT, true)
      style_set_foreground(SCE_RB_COMMENTLINE, Wx::Colour.new('Light Grey'))
      style_set_background(SCE_RB_COMMENTLINE, bg)
      style_set_bold(SCE_RB_COMMENTLINE, true)
      style_set_foreground(SCE_RB_WORD, Wx::Colour.new('Coral'))
      style_set_background(SCE_RB_WORD, bg)
      style_set_bold(SCE_RB_WORD, true)
      style_set_foreground(SCE_RB_OPERATOR, Wx::Colour.new('Light Grey'))
      style_set_background(SCE_RB_OPERATOR, bg)
      style_set_bold(SCE_RB_OPERATOR, true)
      style_set_foreground(SCE_RB_POD, Wx::Colour.new('Grey'))
      style_set_background(SCE_RB_POD, bg)
      style_set_foreground(SCE_RB_NUMBER, Wx::Colour.new('Cyan'))
      style_set_background(SCE_RB_NUMBER, bg)
      style_set_foreground(SCE_RB_STRING, c_str)
      style_set_background(SCE_RB_STRING, bg)
      style_set_foreground(SCE_RB_CHARACTER, Wx::Colour.new('Yellow Green'))
      style_set_background(SCE_RB_CHARACTER, bg)
      style_set_foreground(SCE_RB_SYMBOL, Wx::Colour.new('Gold'))
      style_set_background(SCE_RB_SYMBOL, bg)
      style_set_bold(SCE_RB_SYMBOL, true)
      if Wx::WXWIDGETS_VERSION >= '3.3.0'
        style_set_foreground(SCE_RB_HERE_DELIM, Wx::Colour.new('Coral'))
        style_set_bold(SCE_RB_HERE_DELIM, true)
        style_set_foreground(SCE_RB_HERE_Q, c_str)
        style_set_foreground(SCE_RB_HERE_QQ, c_str)
        style_set_foreground(SCE_RB_HERE_QX, c_str)
        style_set_foreground(SCE_RB_STRING_Q, c_str)
        style_set_foreground(SCE_RB_STRING_QQ, c_str)
        style_set_foreground(SCE_RB_STRING_QX, c_str)
        style_set_foreground(SCE_RB_STRING_QR, c_str)
        style_set_foreground(SCE_RB_STRING_QW, c_str)
      end
      bg = Wx::Colour.new('Cadet Blue')
      fg = Wx::Colour.new('Coral')
      set_fold_margin_colour(true, bg)
      set_fold_margin_hi_colour(true, bg)
      marker_set_foreground(Wx::STC::STC_MARKNUM_FOLDER, fg)
      marker_set_foreground(Wx::STC::STC_MARKNUM_FOLDEROPEN, fg)
      marker_set_foreground(Wx::STC::STC_MARKNUM_FOLDEREND, fg)
      marker_set_foreground(Wx::STC::STC_MARKNUM_FOLDEROPENMID, fg)
    end

    def do_find(txt, forward, whole_word, match_case)
      flags = whole_word ? Wx::STC::STC_FIND_WHOLEWORD : 0
      flags |= Wx::STC::STC_FIND_MATCHCASE if match_case
      if forward
        start_pos = current_pos
        end_pos = length-1
      else
        start_pos = [0, current_pos - (@search_indicator ? @search_indicator.last : 0)].max
        end_pos = 0
      end
      pos, end_pos = find_text(start_pos, end_pos, txt, flags)
      if pos !=Wx::STC::STC_INVALID_POSITION
        [pos, end_pos]
      else
        nil
      end
    end
    private :do_find

    def find(txt, forward, whole_word, match_case)
      find_result = do_find(txt, forward, whole_word, match_case)
      if find_result && find_result.first !=Wx::STC::STC_INVALID_POSITION
        set_indicator_current(IND_SEARCH_MATCH)
        indicator_clear_range(*@search_indicator) if @search_indicator
        @search_indicator = [find_result[0], find_result[1]-find_result[0]]
        indicator_fill_range(*@search_indicator)
        goto_pos(find_result[1])
        @find_replace = true
        true
      else
        false
      end
    end

    def replace(from, to, forward, whole_word, match_case, all=false)
      count = 0
      begin
        find_result = do_find(from, forward, whole_word, match_case)
        if find_result && find_result.first !=Wx::STC::STC_INVALID_POSITION
          set_indicator_current(IND_SEARCH_MATCH)
          indicator_clear_range(*@search_indicator) if @search_indicator
          set_selection(*find_result)
          replace_selection(to)
          @search_indicator = [find_result[0], to.size]
          indicator_fill_range(*@search_indicator)
          goto_pos(@search_indicator.sum)
          count += 1
          @find_replace = true
        else
          return count
        end
      end while all
      return count
    end

    def find_close
      if @search_indicator
        set_indicator_current(IND_SEARCH_MATCH)
        indicator_clear_range(*@search_indicator)
        @search_indicator = nil
      end
    end

    def display_dark(f = true)
      f ? dark_theme : default_theme
    end

    def show_whitespace(f = true)
      set_view_white_space(f ? Wx::STC::STC_WS_VISIBLEALWAYS : Wx::STC::STC_WS_INVISIBLE)
    end

    def show_eol(f = true)
      set_view_eol(f)
    end

    def on_update_ui(_evt)
      @owner.update_ui(self.id)
      unless @find_replace
        find_close
      end
      @find_replace = false
    end

    def on_change(_)
      @owner.update_modify(self.id, get_modify)
    end

    def on_char_added(evt)
      chr =  evt.get_key
      curr_line = get_current_line

      if(chr == 13)
        if curr_line > 0
          line_ind = get_line_indentation(curr_line - 1)
          if line_ind > 0
            set_line_indentation(curr_line, line_ind)
            goto_pos(position_from_line(curr_line)+line_ind)
          end
        end
      end
    end

    def on_margin_click(evt)
      line_num = line_from_position(evt.get_position)
      margin = evt.get_margin

      if(margin == 1)
        toggle_fold(line_num)
      end
    end

  end
  
end
