use crate::atomic;
use crate::iomod::FD;
use crate::lazyset;
/**
 * Copyright (C) 2009 awk4j - https://ja.osdn.net/projects/awk4j/
 * <p>
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * <p>
 * This program 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.
 * <p>
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
use regex::Regex;
use std::sync::atomic::AtomicI32;

const S_STX: &str = "\x02"; // < マーク (ASCII  STX)
const S_BOM: &str = "\u{FFFE}"; // 削除マーク
const S_DELETE: &str = "✔"; // \u{2713}
static REMOVED: AtomicI32 = AtomicI32::new(0);
static IS_PRE: AtomicI32 = AtomicI32::new(0);

/**
 * parse: 空白削除パーサー
 * - Remove whitespace around block-level tags.
 * - rfd.buf: input buffer
 * - rfd.line_number: input line number
 */
pub fn parse(rfd: FD) -> (String, String, String) {
    let input: String = rfd.get_buf();
    let result: String = space(&input);

    let output: String = result.replace(S_BOM, ""); // 出力
    let debug: String = result.replace(S_BOM, S_DELETE); // デバッグ
    let len_result: i32 = result.chars().count() as i32;
    let len_output: i32 = output.chars().count() as i32;
    let incremental: i32 = len_result - len_output; // 空白削除数
    let removes: i32 = atomic::atomic_add(&REMOVED, incremental);
    let message: String = format!("✔removed {} spaces, {} lines", removes, rfd.line_number,);

    println!("{}. {}", rfd.line_number, debug); // debug
    (output, debug, message)
}

/**
 * space: ブロックレベルタグの周辺空白を削除する.
 */
const RE_HTML: &str = r"(\s*)(<(/?)([a-zA-Z]+[1-6]?)[^<>]*(>))(\s*)";
//                      (g1s)(g2(g3:End)(g4:TagName)    (_g5))(g6)
//
fn space(source: &str) -> String {
    lazy_static! { // タグを抽出 (Regex は一度だけコンパイルされる)
        static ref RE: Regex = Regex::new(RE_HTML).unwrap();
    }
    let mut input: String = source.to_string(); // ライフタイムに注意！
    let mut has_indent: bool = true;

    let mut g0: String; // ライフタイム制約により String とする
    let mut g1: String = String::new();
    let mut g2: String = String::new();
    let mut g3: String = String::new();
    let mut g4: String = String::new();
    let mut _g5: String = String::new();
    let mut g6: String = String::new();
    loop {
        let _m = RE.captures(&input);
        match _m {
            Some(caps) => {
                g0 = get_value(&caps[0]);
                g1 = get_value(&caps[1]); // Space
                g2 = get_value(&caps[2]); // ()
                g3 = get_value(&caps[3]); // </
                g4 = get_value(&caps[4]); // Tag name
                _g5 = get_value(&caps[5]); // >
                g6 = get_value(&caps[6]); // Space
            }
            None => g0 = String::new(), // empty
        };
        if g0.is_empty() {
            break;
        }
        let replacement: String; // 初期化すると怒られる
        let tag_name: String = lazyset::find(&g4); // ブロックレベル要素判定
        if !tag_name.is_empty() {
            // ブロックレベル要素なら
            let mut left: &str = &g1; // 左空白
            let mut right: &str = &g6; // 右空白
            if tag_name == "pre" {
                let pre = atomic::atomic_bool_set(&IS_PRE, g3.is_empty()); // pre, /pre
                if pre && !has_indent && !left.is_empty() {
                    left = S_BOM; // <pre>
                }
            }
            if !atomic::atomic_bool_get(&IS_PRE) {
                // (</pre> を含む) <pre> の外のとき
                if !has_indent && !left.is_empty() {
                    left = S_BOM;
                }
                if !right.is_empty() {
                    right = S_BOM;
                }
            }
            replacement = format!("{}{}{}", left, g2, right);
        } else {
            replacement = g0.clone(); // inline-level element.
        }
        // m.appendReplacement(_buffer, replacement);
        // < を STX に置換、これにより次回から正規表現マッチしなくなる
        let _replacement = &replacement.replace("<", S_STX);
        input = RE.replace(&input, _replacement).to_string(); // 置換
        has_indent = false;
    }
    // m.appendTail(_buffer);
    let _input = &input.replace(S_STX, "<");
    _input.to_string()
}

/// 型をまとめて変更可能とするため関数化
fn get_value(x: &str) -> String {
    x.to_string()
}
