# -*- coding: utf-8 -*-
#
#  Copyright (C) 2001, 2002 by Tamito KAJIYAMA
#  Copyright (C) 2002, 2003 by MATSUMURA Namihiko <nie@counterghost.net>
#  Copyright (C) 2004-2019 by Shyouzou Sugitani <shy@users.osdn.me>
#  Copyright (C) 2003-2005 by Shun-ichi TAHARA <jado@flowernet.gr.jp>
#
#  This program is free software; you can redistribute it and/or modify it
#  under the terms of the GNU General Public License (version 2) as
#  published by the Free Software Foundation.  It 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.
#

require 'gettext'
require "gtk4"

require_relative "home"

module Prefs

  RANGE_SCALE = [100, 90, 80, 70, 60, 50, 40, 30, 200, 300, 1000]
  RANGE_SCRIPT_SPEED = [-1, 0, 1, 2, 3, 4, 5, 6, 8] # -1: no wait

  # default settings
  DEFAULT_BALLOON_FONTS = 'Monospace'
  DEFAULT_MENU_FONTS = 'Sans'

  def self.get_default_surface_scale()
    RANGE_SCALE[0]
  end

  def self.get_default_script_speed()
    RANGE_SCRIPT_SPEED[RANGE_SCRIPT_SPEED.length / 2]
  end

  class Preferences

    include GetText

    bindtextdomain("ninix-kagari")

    def initialize(filename)
      @dic = {}
      @filename = filename
      @__stack = {}
    end

    def get(name, default)
      @dic.include?(name) ? @dic[name] : default
    end

    def delete(key)
      @dic.delete(key) if @dic.has_key?(key)
    end

    def include?(name)
      @dic.include?(name) or @__stack.include?(name)
    end

    def set(key, item)
      if @dic.include?(key) and not @__stack.include?(key)
        @__stack[key] = @dic[key]
      end
      @dic[key] = item
    end

    def commit
      @__stack = {}
    end

    def revert
      update(@__stack)
      @__stack = {}
    end

    def update(stack)
      for key in stack.keys
        @dic[key] = stack[key]
      end
    end

    def load
      @dic = {}
      begin
        open(@filename) {|f|
          while line = f.gets
            @dic.store(*line.chomp.split(': ', 2))
          end
        }
      rescue # IOError
        return
      end
    end

    def save
      begin
        Dir.mkdir(File.dirname(@filename), 0755)
      rescue SystemCallError
        #pass
      end
      open(@filename, 'w') {|f|
        keys = @dic.keys.sort
        for key in keys
          if @__stack.include?(key)
            value = @__stack[key]
          else
            value = @dic[key]
          end
          f.write([key, ": ", value, "\n"].join(''))
        end
      }
    end
  end

  class PreferenceDialog

    include GetText

    bindtextdomain("ninix-kagari")

    def initialize
      @parent = nil
      @dialog = Gtk::Window.new
      @dialog.signal_connect('close-request') do |i, *a|
        next response(i, Gtk::ResponseType::DELETE_EVENT)
      end
      @dialog.set_title('Preferences')
      @dialog.set_default_size(-1, 600)
      vbox = Gtk::Box.new(Gtk::Orientation::VERTICAL)
      @dialog.set_child(vbox)
      @notebook = Gtk::Notebook.new()
      @notebook.set_tab_pos(Gtk::PositionType::TOP)
      vbox.append(@notebook)
      @notebook.show()
      @notebook.append_page(make_page_surface_n_balloon(),
                            Gtk::Label.new(_('Surface&Balloon')))
      @notebook.append_page(make_page_misc(),
                            Gtk::Label.new(_('Misc')))
      @notebook.append_page(make_page_debug(),
                            Gtk::Label.new(_('Debug')))
      hbox = Gtk::Box.new(Gtk::Orientation::HORIZONTAL)
      button = Gtk::Button.new(label: 'OK')
      button.signal_connect('clicked') do
        next response(button, Gtk::ResponseType::OK)
      end
      hbox.append(button)
      button = Gtk::Button.new(label: 'Apply')
      button.signal_connect('clicked') do
        next response(button, Gtk::ResponseType::APPLY)
      end
      hbox.append(button)
      button = Gtk::Button.new(label: 'Cancel')
      button.signal_connect('clicked') do
        next response(button, Gtk::ResponseType::CANCEL)
      end
      hbox.append(button)
      vbox.append(hbox)
    end

    def set_responsible(parent)
      @parent = parent
    end

    def load
      filename = Home.get_preferences()
      @__prefs = Preferences.new(filename)
      @__prefs.load()
      reset()
      update() # XXX
      @parent.handle_request(:GET, :notify_preference_changed)
    end

    def save
      @__prefs.save
    end

    def reset
      @fontchooser.set_font(get('balloon_fonts', :default => DEFAULT_BALLOON_FONTS))
      set_default_balloon(get('default_balloon'))
      @menu_fontchooser.set_font(get('menu_fonts', :default => DEFAULT_MENU_FONTS))
      @ignore_button.set_active((not get('ignore_default', :default => 0).zero?))
      scale = get('surface_scale', :default => Prefs.get_default_surface_scale())
      unless RANGE_SCALE.include?(scale)
        @surface_scale_combo.set_active(RANGE_SCALE.index(Prefs.get_default_surface_scale()))
      else
        @surface_scale_combo.set_active(RANGE_SCALE.index(scale))
      end
      script_speed = get('script_speed', :default => Prefs.get_default_script_speed())
      unless RANGE_SCRIPT_SPEED.include?(script_speed)
        @script_speed_combo.set_active(
                                       RANGE_SCRIPT_SPEED.index(Prefs.get_default_script_speed()))
      else
        @script_speed_combo.set_active(RANGE_SCRIPT_SPEED.index(script_speed))
      end
      @balloon_scaling_button.set_active((not get('balloon_scaling').zero?))
      @allowembryo_button.set_active((not get('allowembryo').zero?))
      @check_collision_button.set_active((not get('check_collision', :default => 0).zero?))
      @check_collision_name_button.set_active((not get('check_collision_name', :default => 0).zero?))
      @use_pna_button.set_active((not get('use_pna', :default => 1).zero?))
      @sink_after_talk_button.set_active((not get('sink_after_talk').zero?))
      @raise_before_talk_button.set_active((not get('raise_before_talk').zero?))
      @animation_quality_adjustment.set_value(get('animation_quality', :default => 1.0))
    end

=begin
    NAME_S = Set.new(['sakura_name', # XXX: backward compat
          'sakura_dir', 'default_balloon', 'balloon_fonts', 'menu_fonts'])
    NAME_I = Set.new(['ignore_default', 'script_speed', 'surface_scale',
             'balloon_scaling', 'allowembryo', 'check_collision',
             'check_collision_name', 'use_pna', 'sink_after_talk',
             'raise_before_talk'])
    NAME_F = Set.new(['animation_quality'])
=end
    # NOTE: ruby v3.2では標準ライブラリにSetが無いのでHashで代用
    NAME_S = ['sakura_name', # XXX: backward compat
              'sakura_dir', 'default_balloon', 'balloon_fonts', 'menu_fonts'].map do |x|
      [x, true]
    end.to_h
    NAME_I = ['ignore_default', 'script_speed', 'surface_scale',
             'balloon_scaling', 'allowembryo', 'check_collision',
             'check_collision_name', 'use_pna', 'sink_after_talk',
             'raise_before_talk'].map do |x|
     [x, true]
   end.to_h
   NAME_F = ['animation_quality'].map do |x|
     [x, true]
   end.to_h

    def get(name, default: nil)
      if NAME_S.include?(name)
        value = @__prefs.get(name, default)
      elsif NAME_I.include?(name)
        value = @__prefs.get(name, default).to_i
      elsif NAME_F.include?(name)
        value = @__prefs.get(name, default).to_f
      else # should not reach here
        value = @__prefs.get(name, default)
      end
      return value
    end

    def set_current_sakura(directory)
      key = 'sakura_name' # obsolete
      if @__prefs.include?(key)
        @__prefs.delete(key)
      end
      key = 'sakura_dir'
      if @__prefs.include?(key)
        @__prefs.delete(key)
      end
      @__prefs.set(key, directory)
    end

    def edit_preferences
      show()
    end

    def update(commit: false)
      @__prefs.set('allowembryo', (@allowembryo_button.active? ? 1 : 0).to_s)
      @__prefs.set('balloon_fonts', @fontchooser.font)
      @__prefs.set('menu_fonts', @menu_fontchooser.font)
      Gtk::Settings.default.set_gtk_font_name(@menu_fontchooser.font)
      selected = @balloon_treeview.selection.selected
      unless selected.nil?
        model, listiter = selected
        unless listiter.nil? # XXX
          directory = model.get_value(listiter, 1)
          @__prefs.set('default_balloon', directory)
        end
      end
      @__prefs.set('ignore_default', (@ignore_button.active? ? 1 : 0).to_s)
      @__prefs.set('surface_scale', RANGE_SCALE[@surface_scale_combo.active].to_i.to_s)
      @__prefs.set('script_speed', RANGE_SCRIPT_SPEED[@script_speed_combo.active].to_i.to_s)
      @__prefs.set('balloon_scaling', (@balloon_scaling_button.active? ? 1 : 0).to_s)
      @__prefs.set('check_collision', (@check_collision_button.active? ? 1 : 0).to_s)
      @__prefs.set('check_collision_name', (@check_collision_name_button.active? ? 1 : 0).to_s)
      @__prefs.set('use_pna', (@use_pna_button.active? ? 1 : 0).to_s)
      @__prefs.set('sink_after_talk', (@sink_after_talk_button.active? ? 1: 0).to_s)
      @__prefs.set('raise_before_talk', (@raise_before_talk_button.active? ? 1 : 0).to_s)
      @__prefs.set('animation_quality', @animation_quality_adjustment.value.to_f.to_s)
      if commit
        @__prefs.commit()
      end
    end

    def ok
      hide()
      update(:commit => true)
      @parent.handle_request(:GET, :notify_preference_changed)
    end

    def apply
      update()
      @parent.handle_request(:GET, :notify_preference_changed)
    end

    def cancel
      hide()
      @__prefs.revert()
      reset()
      @parent.handle_request(:GET, :notify_preference_changed)
    end

    def show
      @dialog.show()
    end

    def response(widget, response)
      if response == Gtk::ResponseType::OK
        ok()
      elsif response == Gtk::ResponseType::CANCEL
        cancel()
      elsif response == Gtk::ResponseType::APPLY
        apply
      elsif response == Gtk::ResponseType::DELETE_EVENT
        cancel()
      else # should not reach here
        # pass
      end
      return true
    end

    def hide
      @dialog.hide()
    end

    def make_page_surface_n_balloon
      page = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5)
      page.show()
      frame = Gtk::Frame.new(label=_('Surface Scaling'))
      #page.append(frame, :expand => false, :fill => true, :padding => 0)
      page.append(frame)
      frame.show()
      box = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5)
      frame.set_child(box)
      box.show()
      hbox = Gtk::Box.new(orientation=Gtk::Orientation::HORIZONTAL, spacing=5)
      #box.append(hbox, :expand => false, :fill => true, :padding => 0)
      box.append(hbox)
      hbox.show()
      label = Gtk::Label.new(label=_('Default Setting'))
      #hbox.pack_start(label, :expand => false, :fill => true, :padding => 0)
      hbox.append(label)
      label.show()
      @surface_scale_combo = Gtk::ComboBoxText.new()
      for value in RANGE_SCALE
        @surface_scale_combo.append_text(sprintf("%4d", value))
      end
      #hbox.pack_start(@surface_scale_combo, :expand => false, :fill => true, :padding => 0)
      hbox.append(@surface_scale_combo)
      @surface_scale_combo.show()
      button = Gtk::CheckButton.new(_('Scale Balloon'))
      @balloon_scaling_button = button
      #box.pack_start(button, :expand => false, :fill => true, :padding => 0)
      box.append(button)
      button.show()
      frame = Gtk::Frame.new(label=_('Default Balloon'))
      #page.pack_start(frame, :expand => true, :fill => true, :padding => 0)
      page.append(frame)
      frame.show()
      box = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5)
      frame.set_child(box)
      box.show()
      scrolled = Gtk::ScrolledWindow.new()
      scrolled.set_vexpand(true)
      scrolled.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::ALWAYS)
      #scrolled.set_shadow_type(Gtk::ShadowType::ETCHED_IN)
      #box.pack_start(scrolled, :expand => true, :fill => true, :padding => 0)
      box.append(scrolled)
      scrolled.show()
      treeview = Gtk::TreeView.new()
      column = Gtk::TreeViewColumn.new(_('Balloon Name'),
                                       Gtk::CellRendererText.new(),
                                       :text => 0)
      treeview.append_column(column)
      treeview.selection.set_mode(Gtk::SelectionMode::SINGLE)
      @balloon_treeview = treeview
      scrolled.set_child(treeview)
      treeview.show()
      button = Gtk::CheckButton.new(_('Always Use This Balloon'))
      @ignore_button = button
      #box.pack_start(button, :expand => false, :fill => true, :padding => 0)
      box.append(button)
      button.show()
      frame = Gtk::Frame.new(label=_('Font(s) for balloons'))
      #page.pack_start(frame, :expand => false, :fill => true, :padding => 0)
      page.append(frame)
      frame.show()
      box = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5)
      frame.set_child(box)
      box.show()
      @fontchooser = Gtk::FontButton.new()
      #@fontchooser.set_show_size(false)
      box.append(@fontchooser)
      @fontchooser.show()
      frame = Gtk::Frame.new(label=_('Font(s) for menu'))
      #page.pack_start(frame, :expand => false, :fill => true, :padding => 0)
      page.append(frame)
      frame.show()
      box = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5)
      frame.set_child(box)
      box.show()
      @menu_fontchooser = Gtk::FontButton.new()
      #@menu_fontchooser.set_show_size(false)
      box.append(@menu_fontchooser)
      @menu_fontchooser.show()
      frame = Gtk::Frame.new(label=_('Translucency'))
      #page.pack_start(frame, :expand => false, :fill => true, :padding => 0)
      page.append(frame)
      frame.show()
      box = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5)
      frame.set_child(box)
      box.show()
      button = Gtk::CheckButton.new(_('Use PNA file'))
      @use_pna_button = button
      #box.pack_start(button, :expand => false, :fill => true, :padding => 0)
      box.append(button)
      button.show()
      frame = Gtk::Frame.new(label=_('Animation'))
      #page.pack_start(frame, :expand => false, :fill => true, :padding => 0)
      page.append(frame)
      frame.show()
      box = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5)
      frame.set_child(box)
      box.show()
      hbox = Gtk::Box.new(orientation=Gtk::Orientation::HORIZONTAL, spacing=5)
      box.append(hbox)
      hbox.show()
      label = Gtk::Label.new(label=_('Quality'))
      #hbox.pack_start(label, :expand => false, :fill => true, :padding => 0)
      hbox.append(label)
      label.show()
      @animation_quality_adjustment = Gtk::Adjustment.new(1.0, 0.4, 1.0, 0.1, 0.1, 0)
      button = Gtk::SpinButton.new(adjustment=@animation_quality_adjustment,
                                   climb_rate=0.2, digits=1)
      #hbox.pack_start(button, :expand => false, :fill => true, :padding => 0)
      hbox.append(button)
      button.show()
      hbox.show()
      return page
    end

    def make_page_misc
      page = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5)
      page.show()
      frame = Gtk::Frame.new(label=_('SSTP Setting'))
      #page.pack_start(frame, :expand => false, :fill => true, :padding => 0)
      page.append(frame)
      frame.show()
      button = Gtk::CheckButton.new(_('Allowembryo'))
      @allowembryo_button = button
      frame.set_child(button)
      button.show()
      frame = Gtk::Frame.new(label=_('Script Wait'))
      #page.pack_start(frame, :expand => false, :fill => true, :padding => 0)
      page.append(frame)
      frame.show()
      hbox = Gtk::Box.new(orientation=Gtk::Orientation::HORIZONTAL, spacing=5)
      frame.set_child(hbox)
      hbox.show()
      label = Gtk::Label.new(label=_('Default Setting'))
      #hbox.pack_start(label, :expand => false, :fill => true, :padding => 0)
      hbox.append(label)
      label.show()
      @script_speed_combo = Gtk::ComboBoxText.new()
      for index in 0..(RANGE_SCRIPT_SPEED.length - 1)
        case index
        when 0
          label = _('None')
        when 1
          label = ['1 (', _('Fast'), ')'].join('')
        when RANGE_SCRIPT_SPEED.length - 1
          label = [index.to_s, ' (', _('Slow'), ')'].join('')
        else
          label = index.to_s
        end
        @script_speed_combo.append_text(label)
      end
      #hbox.pack_start(@script_speed_combo, :expand => false, :fill => true, :padding => 0)
      hbox.append(@script_speed_combo)
      @script_speed_combo.show()
      frame = Gtk::Frame.new(label=_('Raise & Lower'))
      #page.pack_start(frame, :expand => false, :fill => true, :padding => 0)
      page.append(frame)
      frame.show()
      box = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5)
      frame.set_child(box)
      box.show()
      button = Gtk::CheckButton.new(_('Sink after Talk'))
      @sink_after_talk_button = button
      #box.pack_start(button, :expand => false, :fill => true, :padding => 0)
      box.append(button)
      button.show()
      button = Gtk::CheckButton.new(_('Raise before Talk'))
      @raise_before_talk_button = button
      #box.pack_start(button, :expand => false, :fill => true, :padding => 0)
      box.append(button)
      button.show()
      return page
    end

    def make_page_debug
      page = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5)
      page.show()
      frame = Gtk::Frame.new(label=_('Surface Debugging'))
      #page.pack_start(frame, :expand => false, :fill => true, :padding => 0)
      page.append(frame)
      frame.show()
      box = Gtk::Box.new(orientation=Gtk::Orientation::VERTICAL, spacing=5)
      frame.set_child(box)
      box.show()
      button = Gtk::CheckButton.new(_('Display Collision Area'))
      @check_collision_button = button
      #box.pack_start(button, :expand => false, :fill => true, :padding => 0)
      box.append(button)
      button.show()
      button = Gtk::CheckButton.new(_('Display Collision Area Name'))
      @check_collision_name_button = button
      #box.pack_start(button, :expand => false, :fill => true, :padding => 0)
      box.append(button)
      button.show()
      return page
    end

    def set_default_balloon(directory)
      model = Gtk::ListStore.new(String, String)
      for name, directory in @parent.handle_request(:GET, :get_balloon_list)
        listiter = model.append()
        model.set_value(listiter, 0, name)
        model.set_value(listiter, 1, directory)
      end
      @balloon_treeview.set_model(model)
      listiter = model.iter_first
      selected = false
      while not listiter.nil?
        value = model.get_value(listiter, 1)
        if value == directory or directory.nil?
          @balloon_treeview.selection.select_iter(listiter)
          selected = true
          break
        end
        listiter.next!
      end
      unless selected
        listiter = model.iter_first
        fail "assert" if listiter.nil?
        @balloon_treeview.selection.select_iter(listiter)
      end
    end
  end
end
