package org.black_ixx.bossshop.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConstructor;
import org.bukkit.configuration.file.YamlRepresenter;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.representer.Representer;

/**
 * <p>自定义的Yml配置文件读取的实现</p>
 * <p>读取的配置文件必须为utf8类型</p>
 * <p>可显式的保存中文</p>
 * <p>不能保存注释</p>
 * @author 聪聪
 *
 */
public class MyYamlConfig extends FileConfiguration{

    protected static final String COMMENT_PREFIX="# ";
    protected static final String BLANK_CONFIG="{}\n";
    private static Yaml yaml;

    static{
        DumperOptions dumpOptions=new DumperOptions();
        Representer yamlRepresenter=new YamlRepresenter();
        dumpOptions.setIndent(2);
        dumpOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
        dumpOptions.setAllowUnicode(true);
        if(!dumpOptions.isAllowUnicode()){
            Class<DumperOptions> clazz=DumperOptions.class;
            try{
                Field field=clazz.getDeclaredField("allowUnicode");
                field.setAccessible(true);
                field.setBoolean(dumpOptions,true);
            }catch(Exception exp){
                MyYamlConfig.log("错误,无法设置文件存储为unicode编码",exp);
            }
        }
        yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
        MyYamlConfig.yaml=new Yaml(new YamlConstructor(),yamlRepresenter,dumpOptions);
    }

    public MyYamlConfig(){}

    /**
     * 在发生异常时用于输出调试信息,或输出警告信息到控制台
     * @param level 错误等级
     * @param msg 错误的提示消息
     * @param thrown 要抛出的异常
     */
    private static void log(String msg,Throwable thrown){
        if(Bukkit.getServer()!=null){
            ConsoleCommandSender tSender=Bukkit.getConsoleSender();
            tSender.sendMessage(ChatColor.RED+"[ERROR] "+msg);
            String[] lines= org.apache.commons.lang.exception.ExceptionUtils.getFullStackTrace(thrown).split("\n");
            for(String sLine : lines) tSender.sendMessage(ChatColor.RED+"[ERROR] "+sLine);
        }else Logger.getLogger("minecraft").log(Level.SEVERE,msg,thrown);
    }

    @Override
    public void addDefault(String path,Object value){
        if(this.get(path)==null) this.set(path,value);
        super.addDefault(path,value);
    }

    /**
     * 保存当前配置内存中的数据为字符串
     */
    public String saveToString(){
        String header=buildHeader();
        String dump=MyYamlConfig.yaml.dump(getValues(false));
        if(dump.equals(BLANK_CONFIG)) dump="";
        return header+dump;
    }

    /**
     * 从字符串中载入配置
     */
    public void loadFromString(String contents) throws InvalidConfigurationException{
        Validate.notNull(contents,"字符串不能未null");
        Map<?,?> input=null;
        try{
            input=(Map<?,?>)MyYamlConfig.yaml.load(contents);
        }catch(YAMLException e){
            throw new InvalidConfigurationException(e);
        }catch(ClassCastException e){
            throw new InvalidConfigurationException("配置文件顶级节点不是Map");
        }
        String header=parseHeader(contents);
        if(header.length()>0){
            options().header(header);
        }
        if(input!=null) convertMapsToSections(input,this);
    }

    /**
     * 将从字符串生成的配置节点数据格式化复制到本实例中
     * @param input 格式化后的数据
     * @param section 本实例,用于递归
     */
    protected void convertMapsToSections(Map<?,?> input,ConfigurationSection section){
        for(Map.Entry<?,?> entry : input.entrySet()){
            String key=entry.getKey().toString();
            Object value=entry.getValue();
            if((value instanceof Map)) convertMapsToSections((Map<?,?>)value,section.createSection(key));
            else section.set(key,value);
        }
    }

    /**
     * 构建配置文件注释头,保存到内存中
     * @param input 文件内容字符串
     * @return 构建完的头
     */
    protected String parseHeader(String input){
        String[] lines=input.split("\r?\n",-1);
        StringBuilder result=new StringBuilder();
        boolean readingHeader=true;
        boolean foundHeader=false;
        for(int i=0;(i<lines.length)&&(readingHeader);i++){
            String line=lines[i];
            if(line.startsWith(COMMENT_PREFIX)){
                if(i>0) result.append("\n");
                if(line.length()>COMMENT_PREFIX.length()){
                    result.append(line.substring(COMMENT_PREFIX.length()));
                }
                foundHeader=true;
            }else if((foundHeader)&&(line.length()==0)){
                result.append("\n");
            }else if(foundHeader){
                readingHeader=false;
            }
        }
        return result.toString();
    }

    /**
     * 保留配置文件的头部注释输出
     */
    protected String buildHeader(){
        String header=options().header();
        if(header==null) return "";
        StringBuilder builder=new StringBuilder();
        String[] lines=header.split("\r?\n",-1);
        boolean startedHeader=false;
        for(int i=lines.length-1;i>=0;i--){
            builder.insert(0,"\r\n");
            if((startedHeader)||(lines[i].length()!=0)){
                builder.insert(0,lines[i]);
                builder.insert(0,COMMENT_PREFIX);
                startedHeader=true;
            }
        }
        return builder.toString();
    }

    /**
     * 保存指定的数据到指定的文件,如果文件不存在将会自动创建
     * <p>保存数据过程中的任何错误都会被记录到控制台然后忽视</p>
     * @param file 指定的文件
     * @param yml 指定的数据
     */
    @Override
    public void save(File file){
        Validate.notNull(file,"配置文件不能为null");
        MyYamlConfig.save(file,this);
    }

    /**
     * 保存指定的数据到指定的文件,如果文件不存在将会自动创建
     * <p>保存数据过程中的任何错误都会被记录到控制台然后忽视</p>
     * @param file 指定的文件
     * @param yml 指定的数据
     */
    public static void save(File file,MyYamlConfig yml){
        Validate.notNull(yml,"配置文件读取器不能为null");
        Validate.notNull(file,"配置文件不能为null");
        FileOutputStream output=null;
        try{
            if(!file.exists()) file.createNewFile();
            output=new FileOutputStream(file,false);
            output.write(yml.saveToString().getBytes("UTF-8"));
        }catch(FileNotFoundException ex){
            MyYamlConfig.log("未找到文件["+file+"]",ex);
        }catch(IOException ex){
            MyYamlConfig.log("无法保存文件["+file+"]",ex);
        }finally{
            if(output!=null) try{
                output.close();
            }catch(IOException exp){}
        }
    }

    /**
     * 为当前实例从给定的文件路径中载入数据
     * <p>
     * 载入配置文件过程中的任何错误都会被记录到控制台然后忽视
     * 如果输入的文件不符合配置文件的格式规范,结果将返回一个空白文件
     * <p>
     * 编码默认使用 UTF-8
     *
     * @param filename 输入的文件路径
     * @throws NullPointerException 如果文件名为空
     */
    @Override
    public void load(String filename){
        MyYamlConfig.load(new File(filename),this);
    }

    /**
     * 为当前实例从给定的文件中载入数据
     * <p>
     * 载入配置文件过程中的任何错误都会被记录到控制台然后忽视
     * 如果输入的文件不符合配置文件的格式规范,结果将返回一个空白文件
     * <p>
     * 编码默认使用 UTF-8
     *
     * @param file 输入文件
     * @throws IllegalArgumentException 如果文件为空
     */
    @Override
    public void load(File file){
        MyYamlConfig.load(file,this);
    }

    /**
     * 为当前实例从给定的流中载入数据,流不会自动关闭
     * <p>
     * 载入配置文件过程中的任何错误都会被记录到控制台然后忽视
     * 如果输入的流不符合配置文件的格式规范,结果将返回一个空白文件
     * <p>
     * 编码默认使用 UTF-8
     *
     * @param stream 输入的数据流
     * @param config 需要载入到的目标,可以为null
     * @return 处理完后的内存中的配置文件
     * @throws IllegalArgumentException 如果输入流为空
     */
    @Override
    public void load(InputStream stream){
        MyYamlConfig.load(stream,this);
    }

    /**
     * 创建一个新的 {@link MyYamlConfig}实例, 并从给定的文件中载入数据
     * <p>
     * 载入配置文件过程中的任何错误都会被记录到控制台然后忽视
     * 如果输入的文件不符合配置文件的格式规范,结果将返回一个空白文件
     * <p>
     * 编码默认使用 UTF-8
     *
     * @param file 输入文件
     * @param config 需要载入到的目标,可以为null
     * @return 处理完后的内存中的配置文件
     * @throws IllegalArgumentException 如果文件为空
     */
    public static MyYamlConfig load(File file,MyYamlConfig config){
        Validate.notNull(file,"文件不能为null");
        if(config==null) config=new MyYamlConfig();
        FileInputStream input=null;
        byte[] contents=new byte[(int)file.length()];
        try{
            input=new FileInputStream(file);
            input.read(contents);
            config.loadFromString(new String(contents,"UTF-8"));
        }catch(FileNotFoundException ex){
            MyYamlConfig.log("无法找到文件["+file+"]",ex);
        }catch(IOException ex){
            MyYamlConfig.log("无法加载文件["+file+"]",ex);
        }catch(InvalidConfigurationException ex){
            MyYamlConfig.log("无法加载文件["+file+"] 配置文件格式错误",ex);
        }finally{
            if(input!=null) try{
                input.close();
            }catch(IOException exp){}
        }
        return config;
    }

    /**
     * 创建一个新的 {@link MyYamlConfig}实例, 并从给定的流中载入数据,流不会自动关闭
     * <p>
     * 载入配置文件过程中的任何错误都会被记录到控制台然后忽视
     * 如果输入的流不符合配置文件的格式规范,结果将返回一个空白文件
     * <p>
     * 编码默认使用 UTF-8
     *
     * @param stream 输入的数据流
     * @param config 需要载入到的目标,可以为null
     * @return 处理完后的内存中的配置文件
     * @throws IllegalArgumentException 如果输入流为空
     */
    public static MyYamlConfig load(InputStream stream,MyYamlConfig config){
        Validate.notNull(stream,"输入流不能为空");
        if(config==null) config=new MyYamlConfig();
        try{
            byte[] contents=new byte[stream.available()];
            config.loadFromString(new String(contents,"UTF-8"));
        }catch(IOException ex){
            MyYamlConfig.log("无法从输入流加载配置",ex);
        }catch(InvalidConfigurationException ex){
            MyYamlConfig.log("无法从输入流加载配置",ex);
        }
        return config;
    }

    /**
     * 设置输入路径的分割用字符,默认为"."
     * @param value 作为分割路径用的字符
     */
    public void pathSeparator(char value){
        this.options().pathSeparator(value);
    }

    /**
     * 获取路径分割用字符
     * @return 分割路径用的字符
     */
    public char pathSeparator(){
        return this.options().pathSeparator();
    }

}
