/*
 * Copyright (C) 2016-2022 ActionTech.
 * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
 */

package com.actiontech.dble.backend.mysql.view;

import com.actiontech.dble.config.model.SystemConfig;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by szf on 2017/10/12.
 */
public class FileSystemRepository implements Repository {
    private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemRepository.class);
    private FileChannel rwChannel = null;
    private String baseDir;
    private String baseName;
    private Map<String, Map<String, String>> viewCreateSqlMap = new HashMap<String, Map<String, String>>();


    public FileSystemRepository() {
        init();
    }

    public FileSystemRepository(Map<String, Map<String, String>> map) {
        init();
        viewCreateSqlMap = map;
    }

    /**
     * init the file read & create the viewMap
     */
    @Override
    public void init() {
        try {
            baseDir = SystemConfig.getInstance().getViewPersistenceConfBaseDir();
            baseName = SystemConfig.getInstance().getViewPersistenceConfBaseName();

            //Judge whether exist the basedir
            createBaseDir();
            //open a channel of the view config file
            RandomAccessFile randomAccessFile = new RandomAccessFile(baseDir + baseName, "rw");
            rwChannel = randomAccessFile.getChannel();

            viewCreateSqlMap = this.getObject();
        } catch (Exception e) {
            LOGGER.info("init view from file error make sure the file is correct :" + e.getMessage());
            e.printStackTrace();
        }
    }

    @Override
    public void terminate() {
        try {
            if (rwChannel != null) {
                rwChannel.close();
            }
        } catch (Exception e) {
            LOGGER.info("close error");
        }
    }

    /**
     * only used by ucore view
     * save the view info into local
     * so the view can be use without ucore
     */
    public void saveMapToFile() {
        try {
            this.writeToFile(mapToJsonString(viewCreateSqlMap));
        } catch (Exception e) {
            LOGGER.warn("ucore view data put local fail:" + e.getMessage());
        }
    }

    /**
     * delete the view info from both fileSystem & memory
     *
     * @param schemaName
     * @param viewName
     */
    @Override
    public void delete(String schemaName, String viewName) {
        try {
            Map<String, Map<String, String>> tmp = new HashMap<String, Map<String, String>>();
            for (Map.Entry<String, Map<String, String>> entry : viewCreateSqlMap.entrySet()) {
                if (entry.getKey().equals(schemaName)) {
                    Map<String, String> tmpSchemaMap = new HashMap<String, String>();
                    for (Map.Entry<String, String> schemaEntry : entry.getValue().entrySet()) {
                        if (!schemaEntry.getKey().equals(viewName.trim())) {
                            tmpSchemaMap.put(schemaEntry.getKey(), schemaEntry.getValue());
                        }
                    }
                    tmp.put(entry.getKey(), tmpSchemaMap);
                    break;
                }
                tmp.put(entry.getKey(), entry.getValue());
            }

            this.writeToFile(mapToJsonString(tmp));

            Map<String, String> schemaMap = viewCreateSqlMap.get(schemaName);
            if (schemaMap == null) {
                schemaMap = new HashMap<String, String>();
                viewCreateSqlMap.put(schemaName, schemaMap);
            }
            schemaMap.remove(viewName.trim());
        } catch (Exception e) {
            LOGGER.warn("delete view from file error make sure the file is correct :" + e.getMessage());
            throw new RuntimeException(e);
        }
    }

    /**
     * put the view info from both fileSystem & memory
     *
     * @param schemaName
     * @param viewName
     * @param createSql
     */
    @Override
    public void put(String schemaName, String viewName, String createSql) {
        try {
            Map<String, Map<String, String>> tmp = new HashMap<String, Map<String, String>>();
            for (Map.Entry<String, Map<String, String>> entry : viewCreateSqlMap.entrySet()) {
                if (entry.getKey().equals(schemaName)) {
                    Map<String, String> tmpSchemaMap = new HashMap<String, String>();
                    for (Map.Entry<String, String> schemaEntry : entry.getValue().entrySet()) {
                        tmpSchemaMap.put(schemaEntry.getKey(), schemaEntry.getValue());
                    }
                    tmpSchemaMap.put(viewName, createSql);
                    tmp.put(entry.getKey(), tmpSchemaMap);
                    break;
                }
                tmp.put(entry.getKey(), entry.getValue());
            }

            Map<String, String> temSchemaMap = tmp.get(schemaName);
            if (temSchemaMap == null) {
                temSchemaMap = new HashMap<String, String>();
                tmp.put(schemaName, temSchemaMap);
                temSchemaMap.put(viewName, createSql);

            }


            this.writeToFile(mapToJsonString(tmp));
            Map<String, String> schemaMap = viewCreateSqlMap.get(schemaName);
            if (schemaMap == null) {
                schemaMap = new HashMap<String, String>();
                viewCreateSqlMap.put(schemaName, schemaMap);
            }
            schemaMap.put(viewName, createSql);


        } catch (Exception e) {
            LOGGER.warn("add view from file error make sure the file is correct :" + e.getMessage());
            throw new RuntimeException("put view data to file error", e);
        }

    }

    /**
     * read the json file and transform into memory
     *
     * @return
     * @throws Exception
     */
    public Map<String, Map<String, String>> getObject() throws Exception {
        Map<String, Map<String, String>> result = new HashMap<String, Map<String, String>>();
        String jsonString = readFromFile();
        if (Strings.isEmpty(jsonString)) {
            return result;
        }
        JsonArray jsonArray = new JsonParser().parse(jsonString).getAsJsonArray();
        if (jsonArray != null) {
            for (JsonElement schema : jsonArray) {
                JsonObject x = schema.getAsJsonObject();
                String schemaName = x.get("schema").getAsString();
                JsonArray viewList = x.get("list").getAsJsonArray();
                Map<String, String> schemaView = new HashMap<String, String>();
                for (JsonElement view : viewList) {
                    JsonObject y = view.getAsJsonObject();
                    schemaView.put(y.get("name").getAsString(), y.get("sql").getAsString());
                }
                result.put(schemaName, schemaView);
            }
        }
        return result;
    }

    /**
     * just truncate the file and writeDirectly new info into
     *
     * @param jsonString
     * @throws Exception
     */
    public void writeToFile(String jsonString) throws Exception {
        ByteBuffer buff = ByteBuffer.wrap(jsonString.getBytes());
        rwChannel.truncate(0);
        rwChannel.write(buff);
        rwChannel.force(true);
    }

    /**
     * read form json file
     *
     * @return
     * @throws Exception
     */
    public String readFromFile() throws Exception {
        FileInputStream fis = null;
        InputStreamReader isr = null;
        BufferedReader br = null;
        try {
            fis = new FileInputStream(baseDir + baseName);
            isr = new InputStreamReader(fis);
            br = new BufferedReader(isr);
            StringBuilder sb = new StringBuilder("");
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            return sb.toString();
        } catch (Exception e) {
            LOGGER.error("can not read file from viewFile", e);
        } finally {
            if (br != null) {
                br.close();
            }
            if (isr != null) {
                isr.close();
            }
            if (fis != null) {
                fis.close();
            }
        }
        return null;
    }


    /**
     * create the log base dir
     */
    private void createBaseDir() {
        File baseDirFolder = new File(baseDir);
        if (!baseDirFolder.exists()) {
            baseDirFolder.mkdirs();
        }
    }


    /**
     * transform the viewMap into JsonString
     *
     * @return
     */
    public String mapToJsonString(Map<String, Map<String, String>> map) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Map<String, String>> schema : map.entrySet()) {
            Map<String, String> schemaSet = schema.getValue();
            sb.append("{\"schema\":\"").append(schema.getKey()).append("\",\"list\":[");
            for (Map.Entry<String, String> view : schemaSet.entrySet()) {
                sb.append("{\"name\":\"").append(view.getKey()).append("\",\"sql\":\"").append(view.getValue()).append("\"},");
            }
            if (',' == sb.charAt(sb.length() - 1)) {
                sb.setCharAt(sb.length() - 1, ']');
            }
            if (schemaSet.size() == 0) {
                sb.append("]");
            }
            sb.append("},");
        }
        if (sb.length() != 0) {
            if (',' == sb.charAt(sb.length() - 1)) {
                sb.setCharAt(sb.length() - 1, ']');
                sb.insert(0, "[");
            }
        }
        return sb.toString();
    }

    @Override
    public Map<String, Map<String, String>> getViewCreateSqlMap() {
        return viewCreateSqlMap;
    }

    public void setViewCreateSqlMap(Map<String, Map<String, String>> viewCreateSqlMap) {
        this.viewCreateSqlMap = viewCreateSqlMap;
    }

}
