import {TEXT_CODING_MAP} from "./scr/charsets.js"
import * as mv3 from "./scr/mv3_util.js"

function allocateRuleId(oldRules){
    function id_occupied(id){
        for (let rule of oldRules){
            if (rule.id ===  id){
                return true
            }
        }
        return false    
    }

    //for historical reason, oldRule IDs can be sparsely large, so we search empty slots instead of increment on old ID
    for (let i = 1; i < 32768; i++){
        if (! id_occupied(i)){
            //console.log(`allocate new rule id = ${i}`)
            return i
        }
    }   

    return 1    //not likely to happen
}

async function getLiveCodeMap(){
    let live_map = {...TEXT_CODING_MAP};

    let items = await chrome.storage.sync.get({
        "disabled_menu": "[]",
        "usr_menu": "[]"
    })

    let disabled_menu = JSON.parse(items["disabled_menu"]);
    let usr_menu = JSON.parse(items["usr_menu"]);    

    disabled_menu.forEach(function(code){
        delete(live_map[code]);
    })
    
    //default menu may be overwritten by user menu if code is the same
    usr_menu.forEach(function({lang, code}){
        live_map[code] = [lang, code, true];    //3rd item as flag of user defined
    }) 
    
    return live_map
}

//extract site url pattern from page url
//this pattern serves as url filter for dynamic rules
function extract_site_url_pattern(url) {
    if (typeof(url) !== "string"){
        return "Newtab";
    }

    var fragments = url.split('/').slice(0, 3);
    return fragments.join("/") + "/*";
}

function injectTxt(txt){
    function _escapeHTML(html) {
        return html.replaceAll('&', '&amp;')
            .replaceAll('<', '&lt;')
            .replaceAll('>', '&gt;')
            .replaceAll('"', '&quot;')
            .replaceAll("'", '&#039;')
    }

    let url = location.href
    let is_html = !!url.match(/\.html?$/i)
    let mime_marker = is_html ? "html" : "plain"        

    let doc_contents = is_html ? txt : `<pre>${_escapeHTML(txt)}</pre>`

    let newDoc = document.open(`text/${mime_marker}`, "replace")
    newDoc.write(doc_contents)
    newDoc.close()
}

//load local file with specified character set
async function loadLocalFile(url, new_code, tabId){
    //XMLHttpRequest() not allowed from file:// scheme
    let response = null
    try{
        response = await fetch(url)
    }
    catch (e){
        chrome.tabs.create({ url: chrome.i18n.getMessage("msgPage")});
        return
    }

    const buf = await response.arrayBuffer()

    let decoder = new TextDecoder(new_code);
    let txt = decoder.decode(buf)

    chrome.scripting.executeScript({
        target: {tabId: tabId},
        func: injectTxt,
        args: [txt],
    });
}

// The onClicked callback function.
async function onClickHandler(info, tab) {
    var new_code = info.menuItemId;

    if (new_code === "help"){
        var locale = chrome.i18n.getMessage("_locale");
        chrome.tabs.create({
            url: "http://www.deep-watch.net/" + locale
                + "/blog/bring-character-encoding-menu-back-to-chrome-with-extension"}
        );
    }
    

    let flg_need_reload = false;  
    let flg_add_rule = true  

    let url_pattern = extract_site_url_pattern(tab.url);

    let oldRules = await chrome.declarativeNetRequest.getDynamicRules()

    let id_to_remove = mv3.findRuleIdByUrl(oldRules, url_pattern)

    if (new_code == "default"){
        flg_need_reload = true;    
        flg_add_rule = false            
    }
    else{
        if (url_pattern == "file:///*"){
            await loadLocalFile(tab.url, new_code, tab.id);
        }
        else{
            flg_need_reload = true;
        }
    }

    if (flg_need_reload){
        //if (flg_add_rule && id_to_remove){
        //    console.log(`reuse rule id = ${id_to_remove}`)
        //}
        //reuse old rule ID to avoid adding duplicated rules for the same site
        let new_id = id_to_remove || allocateRuleId(oldRules)
        await chrome.declarativeNetRequest.updateDynamicRules({
            removeRuleIds : id_to_remove ? [id_to_remove] : [],
            addRules : flg_add_rule ? [mv3.composeRule(new_id, url_pattern, new_code)] : []
        }) 
        
        chrome.tabs.update(tab.id, {url: tab.url});
    }        

}

chrome.contextMenus.onClicked.addListener(onClickHandler);

//update selected coding every time new page loaded
//this action may be expensive
chrome.tabs.onUpdated.addListener(async function (tabId, changeInfo, tab) {
    if (changeInfo.status === "complete") {
        //update context menu
        const g_site2Code = await mv3.get_site2Code()
        var url_pattern = extract_site_url_pattern(tab.url);
        var my_code = g_site2Code[url_pattern] || "default";

        let live_code_map = await getLiveCodeMap()
        
        //update menu status
/*        
        for (var menuId in live_code_map){
            chrome.contextMenus.update(menuId, {
                "checked"   :   false
            })
        }
*/

        if (live_code_map[my_code]){
            chrome.contextMenus.update(my_code, {
                "checked"   :   true
            },
            ()=>{
                let err = chrome.runtime.lastError
                if (err && err.message.indexOf("Cannot find menu item with id") >=0){
                    //for some reason, menu may not be present
                    updateContextMenu().then(()=>{
                        chrome.contextMenus.update(my_code, {
                            "checked"   :   true
                        })                        
                    })
                }
            })
        }
        else{
            //console.warn(`${my_code} not found in menu`)
            let oldRules = await chrome.declarativeNetRequest.getDynamicRules()
            let ruleId = await mv3.findRuleIdByUrl(oldRules, url_pattern)
            if (ruleId){
                //console.log(`remove code ${my_code} for site ${url_pattern}`)
                await chrome.declarativeNetRequest.updateDynamicRules({
                    removeRuleIds : [ruleId]
                })          
            }   
        }
    }
});

async function updateContextMenu(){
    let live_code_map = await getLiveCodeMap()
    await chrome.contextMenus.removeAll()

    chrome.contextMenus.create({
        "title":  chrome.i18n.getMessage("Help_page"),
        "type"  : "normal",
        "contexts":["page"],
        "id": "help"
    });

    chrome.contextMenus.create({
        "type"  : "separator",
        "contexts":["page"],
        "id": "separator"
    });

    for (var [code, [language, ui_code, is_custom]] of Object.entries(live_code_map)) { 
        if (! is_custom){
            language = chrome.i18n.getMessage(language)
        }   
        
        var menu_title = language

        if (ui_code){
            menu_title += " ("+ ui_code + ")";
        }

        chrome.contextMenus.create({
            "title": menu_title,
            "type"  : "radio",
            "contexts":["page"],
            "id": code
        });
    }    

    console.log("Context menu updated")
}

chrome.storage.onChanged.addListener((changes, namespace) => {
    if (namespace === "sync"){
        updateContextMenu().then(()=>{})
    }
})

// Set up context menu at install time.
chrome.runtime.onInstalled.addListener(updateContextMenu)