#
#  tkextlib/blt/tabset.rb
#                               by Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp)
#

require 'tk'
require 'tkextlib/blt.rb'

module Tk::BLT
  class Tabset < TkWindow
    class Tab < TkObject
      include TkTreatItemFont

      TabID_TBL = TkCore::INTERP.create_table

      (TabsetTab_ID = ['blt_tabset_tab'.freeze, '00000'.taint]).instance_eval{
        @mutex = Mutex.new
        def mutex; @mutex; end
        freeze
      }

      TkCore::INTERP.init_ip_env{
        TabID_TBL.mutex.synchronize{ TabID_TBL.clear }
      }

      def self.id2obj(tabset, id)
        tpath = tabset.path
        TabID_TBL.mutex.synchronize{
          if TabID_TBL[tpath]
            TabID_TBL[tpath][id]? TabID_TBL[tpath][id]: id
          else
            id
          end
        }
      end

      def self.new(parent, pos=nil, name=nil, keys={})
        if pos.kind_of?(Hash)
          keys = pos
          name = nil
          pos  = nil
        end
        if name.kind_of?(Hash)
          keys = name
          name = nil
        end
        obj = nil
        TabID_TBL.mutex.synchronize{
          if name && TabID_TBL[parent.path] && TabID_TBL[parent.path][name]
            obj = TabID_TBL[parent.path][name]
            if pos
              if pos.to_s == 'end'
                obj.move_after('end')
              else
                obj.move_before(pos)
              end
            end
            obj.configure if keys && ! keys.empty?
          else
            (obj = self.allocate).instance_eval{
              initialize(parent, pos, name, keys)
              TabID_TBL[@tpath] = {} unless TabID_TBL[@tpath]
              TabID_TBL[@tpath][@id] = self
            }
          end
        }
        obj
      end

      def initialize(parent, pos, name, keys)
        @t = parent
        @tpath = parent.path
        if name
          @path = @id = name
          unless (list(tk_call(@tpath, 'tab', 'names', @id)).empty?)
            if pos
              idx = tk_call(@tpath, 'index', '-name', @id)
              if pos.to_s == 'end'
                tk_call(@tpath, 'move', idx, 'after', 'end')
              else
                tk_call(@tpath, 'move', idx, 'before', pos)
              end
            end
            tk_call(@tpath, 'tab', 'configure', @id, keys)
          else
            pos = 'end' unless pos
            tk_call(@tpath, 'insert', pos, @id, keys)
          end
        else
          pos = 'end' unless pos
          TabsetTab_ID.mutex.synchronize{
            @path = @id = TabsetTab_ID.join(TkCore::INTERP._ip_id_)
            TabsetTab_ID[1].succ!
          }
          tk_call(@tpath, 'insert', pos, @id, keys)
        end
      end

      #def bind(context, cmd=Proc.new, *args)
      #  @t.tab_bind(@id, context, cmd, *args)
      #  self
      #end
      def bind(context, *args)
        # if args[0].kind_of?(Proc) || args[0].kind_of?(Method)
        if TkComm._callback_entry?(args[0]) || !block_given?
          cmd = args.shift
        else
          cmd = Proc.new
        end
        @t.tab_bind(@id, context, cmd, *args)
        self
      end
      #def bind_append(context, cmd=Proc.new, *args)
      #  @t.tab_bind_append(@id, context, cmd, *args)
      #  self
      #end
      def bind_append(context, *args)
        # if args[0].kind_of?(Proc) || args[0].kind_of?(Method)
        if TkComm._callback_entry?(args[0]) || !block_given?
          cmd = args.shift
        else
          cmd = Proc.new
        end
        @t.tab_bind_append(@id, context, cmd, *args)
        self
      end
      def bind_remove(context)
        @t.tab_bind_remove(@id, context)
        self
      end
      def bindinfo(context=nil)
        @t.tab_bindinfo(@id, context)
      end

      def cget(*args)
        @t.tab_cget(@id, *args)
      end
      def cget_strict(*args)
        @t.tab_cget_strict(@id, *args)
      end
      def configure(*args)
        @t.tab_configure(@id, *args)
      end
      def configinfo(*args)
        @t.tab_configinfo(@id, *args)
      end
      def current_configinfo(*args)
        @t.current_tab_configinfo(@id, *args)
      end

      def delete()
        @t.delete(@id)
        TabID_TBL.mutex.synchronize{
          TabID_TBL[@tpath].delete(@id)
        }
        self
      end

      def get_name()
        @id.dup
      end

      def focus()
        @t.focus(self.index)
      end

      def index()
        @t.index_name(@id)
      end

      def invoke()
        @t.invoke(self.index)
      end

      def move_before(idx)
        @t.move_before(self.index, idx)
      end
      def move_after(idx)
        @t.move_after(self.index, idx)
      end

      def perforation_highlight(mode)
        @t.perforation_highlight(self.index, mode)
      end
      def perforation_invoke()
        @t.perforation_invoke(self.index)
      end

      def see()
        @t.see(self.index)
      end

      def tearoff(name=None)
        @t.tab_tearoff(self.index, *args)
      end
    end

    ########################################

    class NamedTab < Tab
      def self.new(parent, name)
        super(parent, nil, name, {})
      end
    end

    ########################################

    include X_Scrollable
    include TkItemConfigMethod

    TkCommandNames = ['::blt::tabset'.freeze].freeze
    WidgetClassName = 'Tabset'.freeze
    WidgetClassNames[WidgetClassName] = self

    def __destroy_hook__
      Tk::BLT::Tabset::Tab::TabID_TBL.mutex.synchronize{
        Tk::BLT::Tabset::Tab::TabID_TBL.delete(@path)
      }
    end

    ########################################

    def __boolval_optkeys
      super() << 'samewidth' << 'tearoff'
    end
    private :__strval_optkeys

    def __strval_optkeys
      super() << 'tabbackground' << 'tabforeground'
    end
    private :__strval_optkeys

    def __item_cget_cmd(id)
      [self.path, 'tab', 'cget', id]
    end
    private :__item_cget_cmd

    def __item_config_cmd(id)
      [self.path, 'tab', 'configure', id]
    end
    private :__item_config_cmd

    def __item_pathname(tagOrId)
      if tagOrId.kind_of?(Tk::BLT::Tabset::Tab)
        self.path + ';' + tagOrId.id.to_s
      else
        self.path + ';' + tagOrId.to_s
      end
    end
    private :__item_pathname

    alias tab_cget itemcget
    alias tab_cget_strict itemcget_strict
    alias tab_configure itemconfigure
    alias tab_configinfo itemconfiginfo
    alias current_tab_configinfo current_itemconfiginfo

    def __item_strval_optkeys(id)
      super(id) << 'shadow'
    end
    private :__item_strval_optkeys

    def tagid(tab)
      if tab.kind_of?(Tk::BLT::Tabset::Tab)
        tab.id
      else
        tab
      end
    end

    def tagindex(tab)
      if tab.kind_of?(Tk::BLT::Tabset::Tab)
        tab.index
      else
        tab
      end
    end

    ########################################

    def activate(index)
      tk_send('activate', tagindex(index))
      self
    end
    alias highlight activate

    #def tabbind(tag, context, cmd=Proc.new, *args)
    #  _bind([path, "bind", tagid(tag)], context, cmd, *args)
    #  self
    #end
    def tabbind(tag, context, *args)
      # if args[0].kind_of?(Proc) || args[0].kind_of?(Method)
      if TkComm._callback_entry?(args[0]) || !block_given?
        cmd = args.shift
      else
        cmd = Proc.new
      end
      _bind([path, "bind", tagid(tag)], context, cmd, *args)
      self
    end
    #def tabbind_append(tag, context, cmd=Proc.new, *args)
    #  _bind_append([path, "bind", tagid(tag)], context, cmd, *args)
    #  self
    #end
    def tabbind_append(tag, context, *args)
      # if args[0].kind_of?(Proc) || args[0].kind_of?(Method)
      if TkComm._callback_entry?(args[0]) || !block_given?
        cmd = args.shift
      else
        cmd = Proc.new
      end
      _bind_append([path, "bind", tagid(tag)], context, cmd, *args)
      self
    end
    def tabbind_remove(tag, context)
      _bind_remove([path, "bind", tagid(tag)], context)
      self
    end
    def tabbindinfo(tag, context=nil)
      _bindinfo([path, "bind", tagid(tag)], context)
    end

    def delete(first, last=None)
      tk_send('delete', tagindex(first), tagindex(last))
      if first.kind_of?(Tk::BLT::Tabset::Tab)
        TabID_TBL.mutex.synchronize{
          TabID_TBL[@path].delete(first.id)
        }
      end
      # middle tabs of the range are unknown
      if last.kind_of?(Tk::BLT::Tabset::Tab)
        TabID_TBL.mutex.synchronize{
          TabID_TBL[@path].delete(last.id)
        }
      end
      self
    end

    def focus(index)
      tk_send('focus', tagindex(index))
      self
    end

    def get_tab(index)
      if (idx = tk_send_without_enc('get', tagindex(index))).empty?
        nil
      else
        Tk::BLT::Tabset::Tab.id2obj(self, idx)
      end
    end
    def get_tabobj(index)
      if (idx = tk_send_without_enc('get', tagindex(index))).empty?
        nil
      else
       Tk::BLT::Tabset::Tab.new(self, nil, name, {})
      end
    end

    def index(str)
      num_or_str(tk_send('index', str))
    end
    def index_name(tab)
      num_or_str(tk_send('index', '-name', tagid(tab)))
    end

    def insert(pos, tab, keys={})
      pos = 'end' if pos.nil?
      Tk::BLT::Tabset::Tab.new(self, tagindex(pos), tagid(tab), keys)
    end
    def insert_tabs(pos, *tabs)
      pos = 'end' if pos.nil?
      if tabs[-1].kind_of?(Hash)
        keys = tabs.pop
      else
        keys = {}
      end
      fail ArgumentError, 'no tabs is given' if tabs.empty?
      tabs.map!{|tab| tagid(tab)}
      tk_send('insert', tagindex(pos), *(tabs + [keys]))
      tabs.collect{|tab| Tk::BLT::Tabset::Tab.new(self, nil, tagid(tab))}
    end

    def invoke(index)
      tk_send('invoke', tagindex(index))
    end

    def move_before(index, base_idx)
      tk_send('move', tagindex(index), 'before', tagindex(base_idx))
      self
    end
    def move_after(index, base_idx)
      tk_send('move', tagindex(index), 'after', tagindex(base_idx))
      self
    end

    def nearest(x, y)
      Tk::BLT::Tabset::Tab.id2obj(self, num_or_str(tk_send_without_enc('nearest', x, y)))
    end

    def perforation_activate(mode)
      tk_send('perforation', 'activate', mode)
      self
    end
    def perforation_highlight(index, *args)
      if args.empty?
        # index --> mode
        tk_send('perforation', 'highlight', index)
      elsif args.size == 1
        # args[0] --> mode
        tk_send('perforation', 'highlight', tagindex(index), args[0])
      else # Error: call to get Tcl's error message
        tk_send('perforation', 'highlight', tagindex(index), *args)
      end
      self
    end
    def perforation_invoke(index=nil)
      if index
        tk_send('perforation', 'invoke', tagindex(index))
      else
        tk_send('perforation', 'invoke')
      end
    end

    def scan_mark(x, y)
      tk_send_without_enc('scan', 'mark', x, y)
      self
    end
    def scan_dragto(x, y)
      tk_send_without_enc('scan', 'dragto', x, y)
      self
    end

    def see(index)
      tk_send('see', tagindex(index))
      self
    end

    def size()
      number(tk_send_without_enc('size'))
    end

    def select(index)
      tk_send('select', tagindex(index))
      self
    end

    def tab_dockall
      tk_send('tab', 'dockall')
      self
    end

    def tab_names(pat=None)
      simplelist(tk_send('tab', 'names', pat)).collect{|name|
        Tk::BLT::Tabset::Tab.id2obj(self, name)
      }
    end

    def tab_objs(pat=None)
      simplelist(tk_send('tab', 'names', pat)).collect{|name|
        Tk::BLT::Tabset::Tab.new(self, nil, name, {})
      }
    end

    def tab_ids(pat=None)
      simplelist(tk_send('tab', 'names', pat))
    end

    def tab_pageheight
      number(tk_send('tab', 'pageheight'))
    end

    def tab_pagewidth
      number(tk_send('tab', 'pagewidth'))
    end

    def tab_tearoff(index, parent=None)
      window(tk_send('tab', 'tearoff', tagindex(index), parent))
    end

    def xscrollcommand(cmd=Proc.new)
      configure_cmd 'scrollcommand', cmd
      self
    end
    alias scrollcommand xscrollcommand

    def xview(*index)
      if index.empty?
        list(tk_send_without_enc('view'))
      else
        tk_send_without_enc('view', *index)
        self
      end
    end
    alias view xview
    alias view_moveto xview_moveto
    alias view_scroll xview_scroll

    alias scrollbar xscrollbar
  end
end
