package com.bocloud.ims.service.utils.excel;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
import org.apache.poi.xssf.usermodel.XSSFDataValidationConstraint;
import org.apache.poi.xssf.usermodel.XSSFDataValidationHelper;
import org.apache.poi.xssf.usermodel.XSSFSheet;

import com.bocloud.common.utils.ListTool;
import com.bocloud.ims.entity.resource.DataCenter;
import com.bocloud.ims.entity.resource.Rack;
import com.bocloud.ims.entity.resource.Room;
import com.bocloud.ims.entity.resource.ServerProperty;
import com.bocloud.ims.repository.resource.DataCenterRepository;
import com.bocloud.ims.repository.resource.RackRepository;
import com.bocloud.ims.repository.resource.RoomRepository;
import com.bocloud.ims.service.utils.ExcelHelper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * 
 * @author liuyuanyuan
 *
 */
public class ExportHelper {
    private DataCenterRepository dataCenterRepository;
    private RoomRepository roomRepository;
    private RackRepository rackRepository;

    public ExportHelper() {
        super();
    }

    public ExportHelper(DataCenterRepository dataCenterRepository, RoomRepository roomRepository,
        RackRepository rackRepository) {
        super();
        this.dataCenterRepository = dataCenterRepository;
        this.roomRepository = roomRepository;
        this.rackRepository = rackRepository;
    }

    /**
     * 生成模板
     * 
     * @param workbook
     * @throws Exception
     */
    public void generateSheet(Workbook workbook) throws Exception {
        XSSFSheet sheet = (XSSFSheet)workbook.createSheet("物理主机(SERVER)");
        // 第一行 分组名称
        Row row0 = sheet.createRow(0);
        // 第一行 属性名称
        Row row1 = sheet.createRow(1);
        // 第二行 属性说明
        Row row2 = sheet.createRow(2);
        // 第三行 正文提示
        Row row3 = sheet.createRow(3);
        row2.setHeight((short)400);

        // 定义颜色
        CellStyle cellStyleGrey = workbook.createCellStyle();
        cellStyleGrey.setFillForegroundColor(HSSFColor.HSSFColorPredefined.GREY_40_PERCENT.getIndex());
        // 填充模式
        cellStyleGrey.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        // 下边框
        cellStyleGrey.setBorderBottom(BorderStyle.THIN);
        // 左边框
        cellStyleGrey.setBorderLeft(BorderStyle.THIN);
        // 右边框
        cellStyleGrey.setBorderRight(BorderStyle.THIN);
        // 居中
        cellStyleGrey.setAlignment(HorizontalAlignment.CENTER);

        // 定义颜色
        CellStyle cellStyleYellow = workbook.createCellStyle();
        cellStyleYellow.setFillForegroundColor(HSSFColor.HSSFColorPredefined.LIGHT_YELLOW.getIndex());
        // 填充模式
        cellStyleYellow.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        // 居中
        cellStyleYellow.setAlignment(HorizontalAlignment.CENTER);
        // 生成表头
        generateHeader(sheet, cellStyleGrey, cellStyleYellow, row0, row1, row2, row3);
        // 合并分组信息
        ExcelHelper.generateMergedRegion(sheet, cellStyleGrey);
        // 设置文本格式以及调整列宽,使之能完整显示字段名称
        ExcelHelper.setTextformatAndColumnwidth(workbook);
    }

    /**
     * 生成表头
     * 
     * @param sheet
     * @param cellStyleGrey
     * @param cellStyleYellow
     * @param row0
     * @param row1
     * @param row2
     * @param row3
     * @throws Exception
     */
    private void generateHeader(XSSFSheet sheet, CellStyle cellStyleGrey, CellStyle cellStyleYellow, Row row0, Row row1,
        Row row2, Row row3) throws Exception {
        // 第一行 分组名称
        Cell cell0 = null;
        // 第一行 属性名称
        Cell cell1 = null;
        // 第二行 属性说明
        Cell cell2 = null;
        // 第三行 正文提示
        Cell cell3 = null;

        // 第一列给出提示start...
        cell0 = row0.createCell(0);
        cell0.setCellValue(new ExcelFactory(row0).newRichTextString("分组信息-->"));
        cell0.setCellStyle(cellStyleYellow);

        cell1 = row1.createCell(0);
        cell1.setCellValue(new ExcelFactory(row1).newRichTextString("属性名称-->"));
        cell1.setCellStyle(cellStyleYellow);

        cell2 = row2.createCell(0);
        cell2.setCellValue(new ExcelFactory(row2).newRichTextString("填写要求-->"));
        cell2.setCellStyle(cellStyleYellow);

        cell3 = row3.createCell(0);
        cell3.setCellValue(new ExcelFactory(row3).newRichTextString("此行开始填写数据-->"));
        cell3.setCellStyle(cellStyleYellow);
        sheet.setColumnWidth(0, "此行开始填写数据".getBytes().length * 256);
        // 第一列给出提示end...

        // 列位置索引
        int index = 1;
        // 设置主机excel表头属性信息
        ArrayList<ServerProperty> serverPropertys = fillHeaderOfServer();

        for (ServerProperty property : serverPropertys) {

            cell0 = row0.createCell(index);
            cell0.setCellValue(new ExcelFactory(row0).newRichTextString(property.getGroupName()));
            cell0.setCellStyle(cellStyleGrey);

            cell1 = row1.createCell(index);
            cell1.setCellValue(new ExcelFactory(row1).newRichTextString(property.getName()));
            cell1.setCellStyle(cellStyleGrey);

            cell2 = row2.createCell(index);
            cell2.setCellValue(new ExcelFactory(row2).newRichTextString(buileNoticeMsg(property)));
            cell2.setCellStyle(cellStyleGrey);

            // 前三列特殊处理，进行三级联动处理
            if (index == 3) {
                List<DataCenter> dataCenters = dataCenterRepository.list(1, Integer.MAX_VALUE, null, null);
                List<Room> rooms = roomRepository.list(1, Integer.MAX_VALUE, null, null);
                List<Rack> racks = rackRepository.list(1, Integer.MAX_VALUE, null, null);
                // 得到数据中心列表
                List<String> dataCenterList = Lists.newArrayList();
                // 得到有子区域的列表
                List<String> areaFatherNameList = Lists.newArrayList();
                // 整理数据，放入一个map中，mapkey存父节点，value存子节点
                Map<String, List<String>> infrastructurehMap = Maps.newHashMap();
                if (!dataCenters.isEmpty()) {
                    for (DataCenter dataCenter : dataCenters) {
                        // 检验数据中心的名称，因为后面的excel新增名称管理器名称时会有规则检验
                        Boolean validateDataCenterName = validateName(dataCenter.getName());
                        if (!validateDataCenterName) {
                            dataCenter.setName("_" + dataCenter.getName());
                        }
                        dataCenterList.add(dataCenter.getName());
                        areaFatherNameList.add(dataCenter.getName());
                        ArrayList<String> roomList = Lists.newArrayList();
                        for (Room room : rooms) {
                            // 检验机房的名称，因为后面的excel新增名称管理器名称时会有规则检验
                            Boolean validateRoomName = validateName(room.getName());
                            if (!validateRoomName) {
                                room.setName("_" + room.getName());
                            }
                            if (room.getDcId().equals(dataCenter.getId())) {
                                roomList.add(room.getName());
                            }
                            ArrayList<String> rackList = Lists.newArrayList();
                            for (Rack rack : racks) {
                                if (rack.getRoomId().equals(room.getId())) {
                                    rackList.add(rack.getName());
                                }
                            }
                            if (!rackList.isEmpty()) {
                                areaFatherNameList.add(room.getName());
                            }
                            if (!infrastructurehMap.containsKey(room.getName())) {
                                infrastructurehMap.put(room.getName(), rackList);
                            }
                        }
                        if (!infrastructurehMap.containsKey(dataCenter.getName())) {
                            infrastructurehMap.put(dataCenter.getName(), roomList);
                        }
                    }
                } else {
                    throw new Exception("当前环境无数据中心数据，请先添加数据中心");
                }
                infrastructurehMap.put("数据中心", dataCenterList);
                // 去重(有子区域的列表会有重复)
                LinkedHashSet<String> hashSet = new LinkedHashSet<String>(areaFatherNameList);
                hashSet.addAll(areaFatherNameList);
                areaFatherNameList.clear();
                areaFatherNameList.addAll(hashSet);
                if (property.getIsQuote()) {
                    setPropertyLinkCell(sheet, index, infrastructurehMap, dataCenterList, areaFatherNameList);
                }
            } else if (index > 3) {
                if (property.isRedio()) {
                    setPropertyIteamCell(sheet, index, property);
                }
            }
            index++;
        }

    }

    /**
     * https://support.office.com/en-us/article/Define-and-use-names-in-formulas-4D0F13AC-53B7-422E-AFD2-ABD7FF379C64#bmsyntax_rules_for_names
     * poi写入excel三级联动数据时，设置名称管理器的名称，其名称的校验规则
     * 
     * @param name
     */
    private Boolean validateName(String name) {

        if (name.length() == 0) {
            return false;
        }
        if (name.length() > 255) {
            return false;
        }
        if ("R".equalsIgnoreCase(name) || "C".equalsIgnoreCase(name)) {
            return false;
        }

        // is first character valid?
        char c = name.charAt(0);
        String allowedSymbols = "_\\";
        boolean characterIsValid = (Character.isLetter(c) || allowedSymbols.indexOf(c) != -1);
        if (!characterIsValid) {
            return false;
        }

        // are all other characters valid?
        allowedSymbols = "_.\\"; // backslashes needed for unicode escape
        for (final char ch : name.toCharArray()) {
            characterIsValid = (Character.isLetterOrDigit(ch) || allowedSymbols.indexOf(ch) != -1);
            if (!characterIsValid) {
                return false;
            }
        }

        // Is the name a valid $A$1 cell reference
        // Because $, :, and ! are disallowed characters, A1-style references become
        // just a letter-number combination
        if (name.matches("[A-Za-z]+\\d+")) {
            String col = name.replaceAll("\\d", "");
            String row = name.replaceAll("[A-Za-z]", "");
            if (CellReference.cellReferenceIsWithinRange(col, row, SpreadsheetVersion.EXCEL97)) {
                return false;
            }
        }

        // Is the name a valid R1C1 cell reference?
        if (name.matches("[Rr]\\d+[Cc]\\d+")) {
            return false;
        }

        return true;
    }

    /**
     * 获取所有下拉选择值
     * 
     * @param sheet
     * @param index
     * @param property
     * @throws Exception
     */
    private void setPropertyIteamCell(Sheet sheet, int index, ServerProperty property) {
        // 获取所有下拉值
        List<String> items = property.getItems();
        if (!ListTool.isEmpty(items)) {
            String[] textlist = new String[items.size()];
            int iteamIndex = 0;
            for (String item : items) {
                textlist[iteamIndex++] = item;
            }
            // 下拉值数量少，不使用新页
            ExcelHelper.setValidation(sheet, textlist, 3, 65535, index, index, false);
        }
    }

    /**
     * 设置数据有效性 引用类型
     * 
     * @param sheet
     * @param module
     * @param index
     * @throws Exception
     */
    private void setPropertyLinkCell(XSSFSheet sheet, int index, Map<String, List<String>> infrastructurehMap,
        List<String> dataCenterList, List<String> areaFatherNameList) {
        Workbook workbook = sheet.getWorkbook();
        // 创建一个专门用来存放地区信息的隐藏sheet页,也不能在现实页之前创建，否则无法隐藏。
        Sheet hideSheet = workbook.createSheet("resource");
        // 将此sheet隐藏
        workbook.setSheetHidden(workbook.getSheetIndex(hideSheet), true);
        int rowId = 0;
        // 设置第一行，存数据中心的信息
        Row dataCenterRow = hideSheet.createRow(rowId++);
        dataCenterRow.createCell(0).setCellValue("数据中心列表");
        for (int i = 0; i < dataCenterList.size(); i++) {
            Cell proviCell = dataCenterRow.createCell(i + 1);
            proviCell.setCellValue(dataCenterList.get(i));
        }
        // 将具体的数据写入到每一行中，行开头为父级区域，后面是子区域。
        for (int i = 0; i < areaFatherNameList.size(); i++) {
            String key = areaFatherNameList.get(i);
            List<String> son = infrastructurehMap.get(key);

            Row row = hideSheet.createRow(rowId++);
            row.createCell(0).setCellValue(key);
            for (int j = 0; j < son.size(); j++) {
                Cell cell = row.createCell(j + 1);
                cell.setCellValue(son.get(j));
            }

            // 添加名称管理器
            String range = getRange(1, rowId, son.size());
            Name name = workbook.createName();
            // key不可重复,将父区域名作为key（key不能以数字开头,所以前面加上"_"解决）
            name.setNameName(key);
            String formula = "resource!" + range;
            name.setRefersToFormula(formula);
        }
        XSSFDataValidationHelper dvHelper = new XSSFDataValidationHelper((XSSFSheet)sheet);
        DataValidationConstraint constraint =
            dvHelper.createExplicitListConstraint(dataCenterList.toArray(new String[] {}));
        // 四个参数分别是：起始行、终止行、起始列、终止列
        CellRangeAddressList provRangeAddressList = new CellRangeAddressList(3, 20, 1, 1);
        DataValidation provinceDataValidation = dvHelper.createValidation(constraint, provRangeAddressList);
        // 验证
        provinceDataValidation.createErrorBox("error", "请选择正确的数据中心");
        provinceDataValidation.setShowErrorBox(true);
        provinceDataValidation.setSuppressDropDownArrow(true);
        sheet.addValidationData(provinceDataValidation);

        // 对前20行设置有效性
        for (int i = 4; i < 22; i++) {
            setDataValidation("A", sheet, i, 2);
            setDataValidation("B", sheet, i, 3);
            setDataValidation("C", sheet, i, 4);
        }
    }

    /**
     * 设置有效性
     * 
     * @param offset
     *            主影响单元格所在列，即此单元格由哪个单元格影响联动
     * @param sheet
     * @param rowNum
     *            行数
     * @param colNum
     *            列数
     */
    private void setDataValidation(String offset, XSSFSheet sheet, int rowNum, int colNum) {
        XSSFDataValidationHelper dvHelper = new XSSFDataValidationHelper(sheet);
        DataValidation data_validation_list;
        data_validation_list =
            getDataValidationByFormula("INDIRECT($" + offset + (rowNum) + ")", rowNum, colNum, dvHelper);
        sheet.addValidationData(data_validation_list);
    }

    /**
     * 加载下拉列表内容
     * 
     * @param formulaString
     * @param naturalRowIndex
     * @param naturalColumnIndex
     * @param dvHelper
     * @return
     */
    private DataValidation getDataValidationByFormula(String formulaString, int naturalRowIndex, int naturalColumnIndex,
        XSSFDataValidationHelper dvHelper) {
        // 加载下拉列表内容
        // 举例：若formulaString = "INDIRECT($A$2)" 表示规则数据会从名称管理器中获取key与单元格 A2 值相同的数据，
        // 如果A2是北京数据中心，那么此处就是北京数据中心下的机房信息。
        XSSFDataValidationConstraint dvConstraint =
            (XSSFDataValidationConstraint)dvHelper.createFormulaListConstraint(formulaString);
        // 设置数据有效性加载在哪个单元格上。
        // 四个参数分别是：起始行、终止行、起始列、终止列
        int firstRow = naturalRowIndex - 1;
        int lastRow = naturalRowIndex - 1;
        int firstCol = naturalColumnIndex - 1;
        int lastCol = naturalColumnIndex - 1;
        CellRangeAddressList regions = new CellRangeAddressList(firstRow, lastRow, firstCol, lastCol);
        // 数据有效性对象
        // 绑定
        XSSFDataValidation data_validation_list = (XSSFDataValidation)dvHelper.createValidation(dvConstraint, regions);
        data_validation_list.setEmptyCellAllowed(false);
        if (data_validation_list instanceof XSSFDataValidation) {
            data_validation_list.setSuppressDropDownArrow(true);
            data_validation_list.setShowErrorBox(true);
        } else {
            data_validation_list.setSuppressDropDownArrow(false);
        }
        // 设置输入信息提示信息
        data_validation_list.createPromptBox("下拉选择提示", "请使用下拉方式选择合适的值！");
        // 设置输入错误提示信息
        // data_validation_list.createErrorBox("选择错误提示", "你输入的值未在备选列表中，请下拉选择合适的值！");
        return data_validation_list;
    }

    /**
     * 计算formula
     * 
     * @param offset
     *            偏移量，如果给0，表示从A列开始，1，就是从B列
     * @param rowId
     *            第几行
     * @param colCount
     *            一共多少列
     * @return 如果给入参 1,1,10. 表示从B1-K1。最终返回 $B$1:$K$1
     * 
     */
    private String getRange(int offset, int rowId, int colCount) {
        char start = (char)('A' + offset);
        if (colCount <= 25) {
            char end = (char)(start + colCount - 1);
            return "$" + start + "$" + rowId + ":$" + end + "$" + rowId;
        } else {
            char endPrefix = 'A';
            char endSuffix = 'A';
            if ((colCount - 25) / 26 == 0 || colCount == 51) {// 26-51之间，包括边界（仅两次字母表计算）
                if ((colCount - 25) % 26 == 0) {// 边界值
                    endSuffix = (char)('A' + 25);
                } else {
                    endSuffix = (char)('A' + (colCount - 25) % 26 - 1);
                }
            } else {// 51以上
                if ((colCount - 25) % 26 == 0) {
                    endSuffix = (char)('A' + 25);
                    endPrefix = (char)(endPrefix + (colCount - 25) / 26 - 1);
                } else {
                    endSuffix = (char)('A' + (colCount - 25) % 26 - 1);
                    endPrefix = (char)(endPrefix + (colCount - 25) / 26);
                }
            }
            return "$" + start + "$" + rowId + ":$" + endPrefix + endSuffix + "$" + rowId;
        }
    }

    /**
     * 生成说明
     * 
     * @param property
     * @return
     */
    private String buileNoticeMsg(ServerProperty property) {
        StringBuilder msg = new StringBuilder();
        if (property.isRequired()) {
            msg.append("必填;");
        } else {
            msg.append("非必填;");
        }
        msg.append("格式[");
        if (property.isRedio()) {
            msg.append("下拉选择];");
        } else if (property.getIsQuote()) {
            msg.append("引用选择];");
        } else if (property.getIsDate()) {
            msg.append("日期];");
        } else {
            msg.append("单行文本];");
        }
        return msg.toString();
    }

    public static ArrayList<ServerProperty> fillHeaderOfServer() {
        ArrayList<ServerProperty> serverPropertys = new ArrayList<>();
        // 主机表头属性分组
        String basicInfo = "基本信息";
        String assetInfo = "资产信息";
        String configInfo = "配置信息";
        // 填充主机表头属性信息
        serverPropertys.add(new ServerProperty("数据中心", basicInfo, false, true, false, false, "dc"));
        serverPropertys.add(new ServerProperty("选择机房", basicInfo, false, true, false, false, "room"));
        serverPropertys.add(new ServerProperty("机柜", basicInfo, false, true, false, false, "rack"));
        // 资产类别item
        List<String> categoryItems = Lists.newArrayList();
        categoryItems.add("裸金属服务器");
        categoryItems.add("X86服务器");
        categoryItems.add("虚拟机服务器");
        categoryItems.add("Power服务器");
        serverPropertys
            .add(new ServerProperty("资产类别", basicInfo, true, false, true, categoryItems, false, "assetCategory"));
        serverPropertys.add(new ServerProperty("设备类别", basicInfo, false, false, false, false, "category"));
        serverPropertys.add(new ServerProperty("管理IP", basicInfo, true, false, false, false, "manageIp"));
        serverPropertys.add(new ServerProperty("设备名称", basicInfo, true, false, false, false, "name"));
        serverPropertys.add(new ServerProperty("服务IP", basicInfo, false, false, false, false, "serviceIp"));
        serverPropertys.add(new ServerProperty("主机名称", basicInfo, true, false, false, false, "hostname"));
        // 系统类别item
        List<String> systemCategoryItems = Lists.newArrayList();
        systemCategoryItems.add("WINDOWS");
        systemCategoryItems.add("LINUX");
        serverPropertys
            .add(new ServerProperty("系统类别", basicInfo, true, false, true, systemCategoryItems, false, "osCategory"));
        serverPropertys.add(new ServerProperty("设备型号", assetInfo, false, false, false, false, "version"));
        serverPropertys.add(new ServerProperty("设备来源", assetInfo, false, false, false, false, "befrom"));
        serverPropertys.add(new ServerProperty("序列号", assetInfo, false, false, false, false, "sn"));
        serverPropertys.add(new ServerProperty("制造商", assetInfo, false, false, false, false, "maker"));
        serverPropertys.add(new ServerProperty("硬件配置", assetInfo, false, false, false, false, "hardware"));
        serverPropertys.add(new ServerProperty("服务合同号", assetInfo, false, false, false, false, "contractNo"));
        serverPropertys.add(new ServerProperty("物理位置", assetInfo, false, false, false, false, "location"));
        serverPropertys.add(new ServerProperty("服务开始时间", assetInfo, false, false, false, true, "serviceStart"));
        serverPropertys.add(new ServerProperty("服务结束时间", assetInfo, false, false, false, true, "serviceEnd"));
        serverPropertys.add(new ServerProperty("资产编号", assetInfo, false, false, false, false, "assetTag"));
        serverPropertys.add(new ServerProperty("服务提供商", assetInfo, false, false, false, false, "vendor"));
        serverPropertys.add(new ServerProperty("服务商电话", assetInfo, false, false, false, false, "vendorPhone"));
        // 所属环境item
        List<String> environmentItems = Lists.newArrayList();
        environmentItems.add("开发环境");
        environmentItems.add("测试环境");
        environmentItems.add("生成环境");
        environmentItems.add("预发环境");
        environmentItems.add("UAT环境");
        serverPropertys
            .add(new ServerProperty("所属环境", assetInfo, false, false, true, environmentItems, false, "environment"));
        serverPropertys.add(new ServerProperty("制造商电话", assetInfo, false, false, false, false, "makerPhone"));
        serverPropertys.add(new ServerProperty("卡槽数量", assetInfo, false, false, false, false, "slotNum"));
        serverPropertys.add(new ServerProperty("卡槽起始点", assetInfo, false, false, false, false, "slotStart"));
        serverPropertys.add(new ServerProperty("用户名", configInfo, false, false, false, false, "username"));
        serverPropertys.add(new ServerProperty("IPMI地址", configInfo, false, false, false, false, "ipmiAddress"));
        serverPropertys.add(new ServerProperty("密码", configInfo, false, false, false, false, "password"));
        serverPropertys.add(new ServerProperty("IPMI端口", configInfo, false, false, false, false, "ipmiPort"));
        serverPropertys.add(new ServerProperty("IPMI账号", configInfo, false, false, false, false, "ipmiAccount"));
        serverPropertys.add(new ServerProperty("主密钥信息", configInfo, false, false, false, false, "keyStore"));
        serverPropertys.add(new ServerProperty("IPMI密码", configInfo, false, false, false, false, "ipmiPassword"));
        serverPropertys.add(new ServerProperty("管理端口", configInfo, false, false, false, false, "sshPort"));
        return serverPropertys;
    }

}
