#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require 'curses'

include Curses
init_screen
noecho
curs_set(0)


# (外部テキストファイルを読み込む) -> 多次元連想配列に変換   { 項目名(Str) => { ページ(Int) => { 項(Str) => 内容(Str), ... }, ... }, ... }
module Kernel

  def sortout(*mrk)
    return self.chomp if mrk.nil? || mrk.empty?
    h = {}
    self.split(mrk[0]).each do |s|
      unless s.empty?
        t = s.split("\n", 2)
        t[0] = t[0].to_i if /\d+/.match?(t[0])
        h[t[0]] = t[1].sortout(*mrk[1..])
      end
    end
    h
  end

  def flatten_hash
    Hash === self ? self.flatten.map { |h| h.flatten_hash }.flatten : self
  end

end

pages = File.read('my_ruby_guide.txt').sortout("####", "###", "##")
notes = File.read('my_ruby_notes.txt').sortout("####", "###", "##")

$data = (pages.to_a + notes.to_a).to_h

pages_title = pages.map { |k, *| k }.sort
notes_title = notes.map { |k, *| k }
titles = pages_title + notes_title


def display(posy = stdscr.cury, posx = stdscr.curx, str) #(Windowが一つなので、) setpos -> addstr -> refresh を一元化
  setpos(posy, posx)
  addstr(str.to_s)
  refresh
end

module Display

  def page
    @page
  end

  def next
    if @page >= @pages_all then @page = 1 else @page += 1 end
  end

  def prev
    if @page <= 1 then @page = @pages_all else @page -= 1 end
  end

  def reset
    @page = 1
  end

  def show
  end

  class List
    include Display

    def initialize(list, name = "", errmsg = "リストが空です")
      @list = list
      @name = name.to_s
      @err_msg = errmsg
      @page = 1
      @pages_all = (@list.size * 0.1).ceil
    end

    def show(posy = 2, posx = 2)
      if @list.empty?
        display(posy, posx, @name + "(0〜9で選択)\t← 1/1 →\n" + "\s" * (posx + 2) + @err_msg)
      else
        display(posy, posx, @name + "(0〜9で選択)\t← #{@page}/#{@pages_all} →" + @list[(@page - 1) * 10 ... @page * 10]
                                                                 .map { |s| "\n" + "\s" * posx + "#{@list.index(s) % 10}.\s" + s}.join)
      end
    end

  end

  class Page
    include Display
    NOTDONE = { 1 => { "Not Done" => "お探しのページはまだ書き途中のようです。\ns4にて最新版の更新をお待ちください。\n m(-_-)m" } }
    NOTFOUND = { 1 => { "Not Found" => "お探しのページは存在しません。\nページ名が変更されたか、未作成の可能性があります。\nBキーを押してもとのページに戻ってください。\n\ns4などで知らせてくれたら、新しくページを作るかもしれません。" } }

    def initialize(title)
      @title = title
      @data = $data.include?(@title) ? ( $data[title] == {} ? NOTDONE : $data[title] ) : NOTFOUND
      @page = 1
      @pages_all = @data.length
    end

    def show
      if @page == @pages_all && @data[@page].include?("関連")
        arr = @data[@page]["関連"].split("\n")
        display(2, 2, @title + "\t← #{@page}/#{@pages_all} →\n\n\s<関連>\n" + arr.map { |s| "  #{arr.index(s)}. " + s + "\n" }.join )
      else
        display(2, 2, @title + "\t← #{@page}/#{@pages_all} →\n" + @data[@page].map { |k, v| "\n\s<" + k + ">\n" + v + "\n" }.join )
      end
    end

  end

end

include Display

List_pages = List.new(pages_title, "項目一覧")
List_notes = List.new(notes_title, "Rubyメモ一覧")

begin

  history = ["menu"]

  loop do

    char = ""
    clear

    case history.last #履歴の最後(=現在のページ)
    when "menu" #----------------------------------

      display(0, 0, "My Ruby Guide へようこそ!
ここは自分(J2405 木村直仁)が、習ったこと・覚えたことをゆるめにデータベース?っぽくまとめた場所です。
Terminalを全画面表示にして使ってください。

Press s: 検索する
      a: 項目一覧を見る
      z: メモ一覧を見る
      q: 終わる")

      loop do
        case getch
        when "s"
          history << "search_page"

        when "a"
          history << "pages_list"
          List_pages.reset

        when "z"
          history << "notes_list"
          List_notes.reset

        when "q"
          history << "end_guide"

        else
          redo
        end
        break
      end

    when "search_page" #---------------------------

      display(1, 2, "検索: ")
      echo
      curs_set(1)
      input = []
      until (char = getch) == 10 #getstrより挙動がいい
        case char
        when 8
          delch unless input.pop.nil?
        else
          input << char.to_s
        end
      end
      keywords = input.join.split
      noecho
      curs_set(0)

      matched = matched_init = matched_part = matched_text = [] #キーワードと 完全に/最初のみ/部分的に/本文中に 一致するものをそれぞれ配列に格納
      keywords.each do |key|

        matched |= titles.select { |s| s == key }

        matched_init |= titles.select { |s| s.match?(key + ".+") }

        matched_part |= titles.select { |s| s.match?(".+" + key + ".*") }

        matched_text |= $data.select { |*, v| v.flatten_hash.any? { |s| String === s && s.match?(".*" + key + ".*") } }.map { |k, *| k }.sort

      end
      search_result = matched | matched_init | matched_part | matched_text #↑の4つを合体
      list_search = List.new(search_result, "検索結果", "キーワードに一致するものが見つかりませんでした") # List に

      loop do
        char = ""
        clear
        display(0, 0, "＜ b: 戻る  他のキー: 再検索")
        display(1, 2, "検索:\s" + keywords.join("\s"))
        list_search.show(3)

        case char = getch
        when 27 #矢印は"^["と"["と"(A〜Dのアルファベット1字)" の三文字、Cursesでは "^["( C-[ ) は 27 になる
          getch
          case getch
          when "C" #→
            list_search.next
          when "D" #←
            list_search.prev
          end
          redo

        when "b"
          history.pop

        else
          num = 10 * (list_search.page - 1) + char.to_i
          if /\d/.match?(char.to_s) && search_result.length > num
            history << search_result[num]
          end

        end
        break
      end

    when "pages_list" #----------------------------

      loop do
        char = ""
        clear
        display(0, 0, "＜ b: 戻る   0〜9: ページを表示   他のキー: 再表示")
        List_pages.show

        case char = getch
        when 27 #矢印
          getch
          case getch
          when "C" #→
            List_pages.next
          when "D" #←
            List_pages.prev
          end
          redo

        when "b"
          history.pop

        else
          num = 10 * (List_pages.page - 1) + char.to_i
          if /\d/.match?(char.to_s) && pages_title.length > num
            history << pages_title[num]
          end

        end
        break
      end

    when "notes_list" #----------------------------

      loop do
        char = ""
        clear
        display(0, 0, "＜ b: 戻る  r: 再表示")
        List_notes.show

        case char = getch
        when 27 #矢印
          getch
          case getch
          when "C" #→
            List_notes.next
          when "D" #←
            List_notes.prev
          end
          redo

        when "b"
          history.pop

        else
          num = 10 * (List_notes.page - 1) + char.to_i
          if /\d/.match?(char) && notes_title.length > num
            history << notes_title[num]
          end

        end
        break
      end

    when "end_guide" #-----------------------------

      break

    else #-----------------------------------------

      page = Page.new(history.last) #履歴の最後(=現在のページ)を Page のインスタンスに
      loop do
        clear
        display(0, 0, "＜ b: 戻る   0〜9: 関連ページへ   他のキー: 再表示")
        page.show

        case char = getch #入力
        when 27 #矢印
          getch
          case getch
          when "C" #→
            page.next
          when "D" #←
            page.prev
          end

        when "b"
          history.pop
          break

        else
          unless $data[history.last].nil? #存在するページだったら

            curpage = $data[history.last][page.page]

            if /\d/.match?(char.to_s) && curpage.map { |k, v| k }.any? { |s| /関連.*/.match?(s) } && curpage.length == 1 #入力が数字 && 項目が "関連〜" 一つのみある

              arr = curpage.to_a[0][1].split("\n")
              if arr.length > char.to_i
                history << arr[char.to_i]
                break
              end

            end

          end
        end

      end

    end

  end

ensure
  close_screen
  puts "終了します。"
end
