/**
 * Created by G-Canvas Open Source Team.
 * Copyright (c) 2017, Alibaba, Inc. All rights reserved.
 *
 * This source code is licensed under the Apache Licence 2.0.
 * For the full copyright and license information, please view
 * the LICENSE file in the root directory of this source tree.
 */
package com.taobao.gcanvas;

import android.os.Build;
import android.util.Log;

import com.taobao.gcanvas.util.GLog;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

public class GFontConfigParser {

    // Android font families path
    private static final String SYSTEM_FONTS_FILE = "/system/etc/system_fonts.xml";
    private static final String SYSTEM_FONTS_FILE_LOLLIPOP = "/system/etc/fonts.xml";
    // TODO: Fall-back fonts are not used yet. Leave the code here since we may do it in future.
    private static final String FALLBACK_FONTS_FILE = "/system/etc/fallback_fonts.xml";
    // Fallback font
    private static final String FALLBACK_FONT = "DroidSansFallback.ttf";
    // Fonts location
    private static final String SYSTEM_FONT_LOCATION = "/system/fonts/";

    private boolean mIsInitialized = false;
    private HashMap<List<String>, List<String>> mFontFamilies = null;
    private List<String> mFallbackFonts = null;

    public GFontConfigParser() {
        if (!mIsInitialized) {
            readConfigFile();
            readFallbackConfigFile();
            mIsInitialized = true;
        }
    }

    public HashMap<List<String>, List<String>> getFontFamilies() {
        return mFontFamilies;
    }

    public List<String> getFallbackFontsList() {
        return mFallbackFonts;
    }

    public String getFallbackFont() {
        //vivo
        if (new File(SYSTEM_FONT_LOCATION + "DroidSansFallbackBBK.ttf").exists()) {
            return "DroidSansFallbackBBK.ttf";
        } else if (new File(SYSTEM_FONT_LOCATION + "NotoSansHans-Regular.otf").exists()) {
            //meizu flyme4.0
            return "NotoSansHans-Regular.otf";
        } else if (new File(SYSTEM_FONT_LOCATION + "NotoSansSC-Regular.otf").exists()) {
            return "NotoSansSC-Regular.otf";
        } else if (new File(SYSTEM_FONT_LOCATION + "NotoSansCJK-Regular.ttc").exists()) {
            return "NotoSansCJK-Regular.ttc";
        } else if (new File(SYSTEM_FONT_LOCATION + FALLBACK_FONT).exists()) {
            return FALLBACK_FONT;
        } else {
            return "DroidSans.ttf";
        }

    }

    public String getSystemFontLocation() {
        return SYSTEM_FONT_LOCATION;
    }

    private void readFallbackConfigFile() {
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = null;
            db = dbf.newDocumentBuilder();

            File file = new File(FALLBACK_FONTS_FILE);
            if (!file.exists()) {
                return;
            }

            Document doc = db.parse(file);
            parseFallbackXML(doc);
        } catch (Exception e) {
            Log.e("GFontConfigParser", "readFallbackConfigFile " + e.getMessage());
        }
    }

    private void readConfigFile() {
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = null;
            db = dbf.newDocumentBuilder();

            Document doc;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                doc = db.parse(new File(SYSTEM_FONTS_FILE_LOLLIPOP));
            } else {
                doc = db.parse(new File(SYSTEM_FONTS_FILE));
            }
            parseXML(doc);
        } catch (Exception e) {
            GLog.e("GFontConfigParser", e.getMessage());
        }
    }

    private void parseXML(Document doc) throws Exception {
        if (mFontFamilies == null) {
            mFontFamilies = new HashMap<List<String>, List<String>>();
        } else {
            mFontFamilies.clear();
        }

        // family set - root node
        Element root = doc.getDocumentElement();
        if (!root.getTagName().equals("familyset")) {
            GLog.w("GFontConfigParser", "Can't find familyset.");
            return;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            List<String> nameList = null;
            List<String> aliasNameList = null;
            List<String> fileList = null;

            //leon
            NodeList nodeList = root.getElementsByTagName("family");
            int length = nodeList.getLength();
            for (int i = 0; i < length; ++i) {
                Node familyNode = nodeList.item(i);

                // Acquire name-set node and file-set node
                NamedNodeMap n = familyNode.getAttributes();

                Node s = familyNode.getAttributes().getNamedItem("name");
                if (s == null) {
                    break;
                } else {
                    //GLog.w("GFontConfigParser", "family name: " + s.getNodeValue());
                }

                NodeList fontNodes = ((Element) familyNode).getElementsByTagName("font");

                if (fontNodes == null) {
                    GLog.w("GFontConfigParser", "nameset or fileset is invalid.");
                    break;
                }

                // Get the length of name-set, file-set.
                int namesetLength = fontNodes.getLength();

                nameList = new ArrayList<String>();
                nameList.add(s.getNodeValue());

                fileList = new ArrayList<String>();
                // Get all file name
                for (int j = 0; j < namesetLength; ++j) {
                    Node fileNode = fontNodes.item(j);
                    if (fileNode instanceof Element) {
                        Element fileElement = (Element) fileNode;
                        String text = fileElement.getTextContent();
                        fileList.add(text);
                    }
                }

                //alias
                NodeList aliasList = root.getElementsByTagName("alias");
                int aliasLength = aliasList.getLength();
                for (int k = 0; k < aliasLength; ++k) {
                    Node aliasNode = aliasList.item(k);

                    Node nameA = aliasNode.getAttributes().getNamedItem("name");
                    Node toA = aliasNode.getAttributes().getNamedItem("to");
                    if (nameA == null || toA == null) {
                        break;
                    } else {
                        //GLog.w("GCanvas - GFontConfigParser", "nameA: " + nameA + " toA: " + toA);
                    }

                    aliasNameList = new ArrayList<String>();
                    for (String name : nameList) {
                        if (name.equals(toA.getNodeValue())) {
                            aliasNameList.add(nameA.getNodeValue());
                            //GLog.w("GCanvas - GFontConfigParser", "alias " + nameA.getNodeValue() + " to: " + name);
                        }
                    }
                    nameList.addAll(aliasNameList);
                }

                // Add fonts, files to HashMap.
                mFontFamilies.put(nameList, fileList);
            }

            //leon
        } else {

            NodeList nodeList = root.getElementsByTagName("family");
            int length = nodeList.getLength();
            for (int i = 0; i < length; ++i) {
                Node familyNode = nodeList.item(i);

                // Acquire name-set node and file-set node
                Node namesetNode = null;
                Node filesetNode = null;

                if (familyNode instanceof Element) {
                    Element familyElement = (Element) familyNode;
                    NodeList namesetNodes = familyElement.getElementsByTagName("nameset");
                    NodeList filesetNodes = familyElement.getElementsByTagName("fileset");

                    if (namesetNodes == null
                            || filesetNodes == null
                            || namesetNodes.getLength() != 1
                            || filesetNodes.getLength() != 1) {
                        GLog.w("GFontConfigParser", "nameset or fileset node doesn't exist.");
                        return;
                    }

                    namesetNode = namesetNodes.item(0);
                    filesetNode = filesetNodes.item(0);
                }

                if (namesetNode == null || filesetNode == null) {
                    GLog.w("GFontConfigParser", "nameset or fileset is invalid.");
                    return;
                }

                // Get the node list.
                NodeList nameset = namesetNode.getChildNodes();
                NodeList fileset = filesetNode.getChildNodes();
                // Return if no node is found
                if (nameset == null || fileset == null) {
                    GLog.w("GFontConfigParser", "nameset or fileset is empty.");
                    return;
                }

                // Get the length of name-set, file-set.
                int namesetLength = nameset.getLength();
                int filesetLength = fileset.getLength();
                List<String> nameList = new ArrayList<String>();
                List<String> fileList = new ArrayList<String>();

                // Get all file name
                for (int j = 0; j < filesetLength; ++j) {
                    Node fileNode = fileset.item(j);
                    if (fileNode instanceof Element) {
                        Element fileElement = (Element) fileNode;
                        if (fileElement.getTagName().equals("file")) {
                            String text = fileElement.getTextContent();
                            fileList.add(text);
                        }
                    }
                }

                // Get all font names.
                for (int j = 0; j < namesetLength; ++j) {
                    Node nameNode = nameset.item(j);
                    if (nameNode instanceof Element) {
                        Element nameElement = (Element) nameNode;
                        if (nameElement.getTagName().equals("name")) {
                            String text = nameElement.getTextContent();
                            nameList.add(text);
                        }
                    }
                }

                // Add fonts, files to HashMap.
                mFontFamilies.put(nameList, fileList);
            }
        }
    }

    private void parseFallbackXML(Document doc) throws Exception {
        if (mFallbackFonts == null) {
            mFallbackFonts = new ArrayList<String>();
        } else {
            mFallbackFonts.clear();
        }

        // family set - root node
        Element root = doc.getDocumentElement();
        if (!root.getTagName().equals("familyset")) {
            GLog.w("GFontConfigParser", "Can't find familyset.");
            return;
        }

        NodeList nodeList = root.getElementsByTagName("family");
        int length = nodeList.getLength();
        for (int i = 0; i < length; ++i) {
            Node familyNode = nodeList.item(i);

            Node filesetNode = null;

            if (familyNode instanceof Element) {
                Element familyElement = (Element) familyNode;
                NodeList filesetNodes = familyElement.getElementsByTagName("fileset");

                if (filesetNodes == null || filesetNodes.getLength() != 1) {
                    GLog.w("GFontConfigParser", "nameset or fileset node doesn't exist.");
                    return;
                }

                filesetNode = filesetNodes.item(0);
            }

            if (filesetNode == null) {
                GLog.w("GFontConfigParser", "nameset or fileset is invalid.");
                return;
            }

            NodeList fileset = filesetNode.getChildNodes();
            if (fileset == null) {
                GLog.w("GFontConfigParser", "nameset or fileset is empty.");
                return;
            }

            int filesetLength = fileset.getLength();
            for (int j = 0; j < filesetLength; ++j) {
                Node fileNode = fileset.item(j);
                if (fileNode instanceof Element) {
                    Element fileElement = (Element) fileNode;
                    if (fileElement.getTagName().equals("file")) {
                        String text = fileElement.getTextContent();
                        mFallbackFonts.add(text);
                    }
                }
            }
        }
    }
}
