# GinzaをRから使う関数
# 以下の条件を満たすマシンでのみ動作する
# 1) reticulateパッケージを導入済み
# 2) pythonを導入済み
# 3) pythonでja_ginzaを導入済み
# 動作しているpython環境，バージョンの確認にはreticulateパッケージpy_config()関数を用いる
# オプション：
# mode：A，B，C（語の区切りが短，中，長）
# dic：small，core，full（辞書のサイズ；それぞれを予めインストールしておく必要がある；学習にはcoreが使われているので，公式にはcore推奨）

# SpacyとGinzaを設定する関数
setSpacy <- function(mode = "C", dic = "core", model = "ja_ginza"){
    spacypy <- reticulate::import(module = "spacy")
    ginzapy <- reticulate::import(module = "ginza")
    sudachipy <- reticulate::import(module = "sudachipy")
    tokenizer_obj <- sudachipy$dictionary$Dictionary(dict_type = dic)$create()

    nlp <- spacypy$load(model)
    nlp$tokenizer$tokenizer <- tokenizer_obj
    ginzapy$set_split_mode(nlp, mode)
    return(list(ginzapy = ginzapy, nlp = nlp))
}


# メイン関数
# target：分析対象となる文字列
ginzaru <- function(target, mode = "C", dic = "core", model = "ja_ginza"){
    spacySet <- setSpacy(mode = mode, dic = dic, model = model)
    ginzapy <- spacySet$ginzapy
    nlp <- spacySet$nlp

    doc <- nlp(target)
    analyzedmat <- do.call("rbind", lapply(1:length(doc), function(x) getToken(doc[x-1])))
    analyzeddat <- as.data.frame(analyzedmat)
    return(analyzeddat)
}


# docからトークン情報を取り出す関数
getToken <- function(res_part){
    inft <- res_part$morph$get("Inflection")
    tokenv <- c("no" = res_part$i, 
#        "Orth" = res_part$orth_, 
        "Text" = res_part$text, 
        "Lemma" = res_part$lemma_, 
        "POS" = res_part$pos_, 
        "Tag" = res_part$tag_, 
        "Inflection" = replace(inft, typeof(inft) == "list", NA)[[1]], 
        "Reading" = paste0(res_part$morph$get("Reading"), collapse = ""), 
        "Norm" = res_part$norm_, 
        "Shape" = res_part$shape_, 
        "Alpha" = res_part$is_alpha, 
#        "Punct" = res_part$is_punct, 
#        "Quote" = res_part$is_quote, 
        "Stop" = res_part$is_stop, 
        "Dep" = res_part$dep_, 
        "Head" = res_part$head$text, 
        "HeadID" = res_part$head$i, 
        "children" = paste0("[", paste(reticulate::iterate(res_part$children, f = function(x) x$text), collapse = ", "), "]"))
    return(tokenv)
}


# MeCab式の解析結果を返す関数
# target：分析対象となる文字列
ginzame <- function(target, mode = "C", dic = "core", model = "ja_ginza"){
    spacySet <- setSpacy(mode = mode, dic = dic, model = model)
    ginzapy <- spacySet$ginzapy
    nlp <- spacySet$nlp

    doc <- nlp(target)
    analyzedmat <- do.call("rbind", lapply(1:length(doc), function(x) getToken2(doc[x-1])))
    analyzeddat <- as.data.frame(analyzedmat)
    return(analyzeddat)
}


# docからトークン情報を取り出す関数2（ginzame用）
getToken2 <- function(res_part){
    inft <- res_part$morph$get("Inflection")
    if(typeof(inft) == "list"){
        inftv <- c(NA, NA)
    }else{
        inftv <- strsplit(inft, ";")[[1]]
    }
    posv <- strsplit(res_part$tag_, "-")[[1]]
    posvsup <- c(posv, rep(NA, 4 - length(posv)))
    tokenv <- c("Surface_Value" = res_part$text, 
        "Part_of_Speech" = posvsup[1], 
        "Part_of_Speech1" = posvsup[2], 
        "Part_of_Speech2" = posvsup[3], 
        "Part_of_Speech3" = posvsup[4], 
        "Conjugation" = inftv[1], 
        "Inflection" = inftv[2], 
        "Root_Form" = res_part$norm_,
        "Reading" = paste0(res_part$morph$get("Reading"), collapse = ""), 
        "Pronunciation" = NA)
    return(tokenv)
}


# 文ごとに分割する関数
# target：分析対象となる文字列
ginsep <- function(target, mode = "C", dic = "core", model = "ja_ginza"){
    spacySet <- setSpacy(mode = mode, dic = dic, model = model)
    ginzapy <- spacySet$ginzapy
    nlp <- spacySet$nlp

    doc <- nlp(target)
    sentences <- reticulate::iterate(doc$sents)
    return(sentences)
}


# 固有表現抽出を行う関数
# target：分析対象となる文字列
# add：追加で判定したい固有表現を挙げたデータフレームを指定する；このデータフレームには，一列目にlabel，二列目にpatternを並べる
ginent <- function(target, mode = "C", dic = "core", model = "ja_ginza", add = NULL){
    spacySet <- setSpacy(mode = mode, dic = dic, model = model)
    ginzapy <- spacySet$ginzapy
    nlp <- spacySet$nlp
    ginzapy$set_split_mode(nlp, mode)

    # ルール追加
    if(!is.null(add)){
        ruler <- nlp$add_pipe("entity_ruler")
        ruler$add_patterns(apply(add, 1, function(x) reticulate::dict("label" = x[1], "pattern" = x[2])))
    }

    # 解析
    doc <- nlp(target)
    entmat <- do.call("rbind", lapply(doc$ents, function(x) c(x$text, x$label_, x$start_char, x$end_char)))
    if(is.null(entmat)){
        return(NA)
    }else{
        entdat <- as.data.frame(entmat)
        colnames(entdat) <- c("Text", "Label", "StartChar", "EndChar")
        return(entdat)
    }
}


# 文節に分ける関数
# target：分析対象となる文字列
ginbun <- function(target, mode = "C", dic = "core", model = "ja_ginza", position = FALSE){
    spacySet <- setSpacy(mode = mode, dic = dic, model = model)
    ginzapy <- spacySet$ginzapy
    nlp <- spacySet$nlp

    doc <- nlp(target)
    if(position){
        bunsetsu <- ginzapy$bunsetu_position_types(doc)
    }else{
        bunsetsu <- ginzapy$bunsetu_spans(doc)
    }
    return(bunsetsu)
}


# 名詞句を取り出す関数
# target：分析対象となる文字列
ginnoun <- function(target, mode = "C", dic = "core", model = "ja_ginza"){
    spacySet <- setSpacy(mode = mode, dic = dic, model = model)
    ginzapy <- spacySet$ginzapy
    nlp <- spacySet$nlp

    doc <- nlp(target)
    nouns <- reticulate::iterate(doc$noun_chunks)
    return(nouns)
}


# コサイン類似度を計算する関数
# text1：分析対象となる文字列1
# text2：分析対象となる文字列2
# modelpath：単語ベクトルを追加したカスタムモデルのパス
# - いずれの文字列もベクトルを指定することが可能
ginsim <- function(text1, text2, mode = "C", dic = "core", model = "ja_ginza", modelpath = NA){
    spacySet <- setSpacy(mode = mode, dic = dic, model = model)
    ginzapy <- spacySet$ginzapy
    nlp <- spacySet$nlp

    if(!is.na(modelpath)){
        nlp$from_disk(modelpath)
    }

    doc1 <- lapply(text1, function(x) nlp(x))
    simmat <- lapply(doc1, function(x) sapply(text2, function(y) x$similarity(nlp(y))))
    simscore <- do.call("rbind", simmat) |> as.data.frame()
    row.names(simscore) <- text1
    return(simscore)
}


# 単語ベクトルを入れ替えてカスタムモデルを生成する関数
# pythonでgensimをインストールしておく必要がある
# sourcepath：単語ベクトルへのパスを指定
# outpath：出力先のパスを指定
# - モデルのサイズにもよるが非常に時間がかかることに注意
createModel <- function(sourcepath, outpath, mode = "C", dic = "core", model = "ja_ginza"){
    spacySet <- setSpacy(mode = mode, dic = dic, model = model)
    ginzapy <- spacySet$ginzapy
    nlp <- spacySet$nlp

    gensimpy <- reticulate::import(module = "gensim.models")
    wvm <- gensimpy$KeyedVectors$load_word2vec_format(sourcepath)
    nlp$vocab$reset_vectors(width = ncol(wvm$vectors))
    for(i in wvm$index_to_key){
        nlp$vocab$set_vector(i, wvm[i])
    }
    cat(nlp$vocab$vectors$shape)# 語彙数と次元数の確認
    nlp$to_disk(outpath)
}
