package com.zjty.inspect.inspect;

import com.zjty.inspect.dao.*;
import com.zjty.inspect.entity.*;
import com.zjty.inspect.enums.DependenceManagement;
import com.zjty.inspect.enums.Framework;
import com.zjty.inspect.enums.LanguageEnum;
import com.zjty.inspect.enums.RecastMethod;
import com.zjty.inspect.service.RuleService;
import com.zjty.inspect.thread.task.AsyncTask;
import com.zjty.inspect.utils.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static java.util.concurrent.CompletableFuture.allOf;
import static java.util.concurrent.CompletableFuture.runAsync;

/**
 * 项目体检，根据既定特征值，
 * 扫描、统计、分析项目特征，
 * 生成报告VO
 *
 * @author mcj
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)
public class Inspector {

    @Autowired
    private AsyncTask asyncTask;

    private AnalysisFile analysisFile;
    private BudgetUitl budgetUitl;

    private RuleService ruleService;
    private TechnologyDao technologyDao;
    private ParameterDao parameterDao;

    private DependencyVo dependencyVo = new DependencyVo();
    private ArrayList<Rule> rules = new ArrayList<>(512);
    private ArrayList<Warn> warns = new ArrayList<>(64);
    private ArrayList<Warn> supportWarns = new ArrayList<>(64);

    /**
     * 添加规则时去重使用
     * key：mysql-connect  :  *
     * mysql-connect:*
     * mysql-connect:.java
     * <p>
     * value:随意
     */
    private HashMap<String, Rule> ruleMap = new HashMap<>(512);

    /**
     * 支持的国产化技术
     */
    private Technology techJavaSupport;
    /**
     * 不支持的国产化技术
     */
    private Technology techNotCnSupport;

    /**
     * 未知依赖
     */
    private Technology techUnKnowSupport;
    /**
     * 计算预算的参数对象
     */
    private InspectParameter inspectParameter;

    /**
     * 源代码大小
     */
    private double codeSize = 0;

    /**
     * 文件数量
     */
    private Integer fileNum = 0;
    /**
     * 文件行数
     */
    private Integer fileLine = 0;
    /**
     * 报告对象
     */
    private ReportVo report;

    /**
     * 统计语言
     * key：java
     * value：10
     */
    private Map<String, Counter> languageMatchMap = new HashMap<>(16);

    /**
     * 后缀语言
     * key：properties
     * value:[{/Users/path},{/Users/path}]
     */
    private Map<String, LanguageEnum> suffixLanguageMapping = new HashMap<>(16);

    /**
     * 规则列表
     */
    private List<Rule> ruleList;

    /**
     * 配置文件后缀
     * xml：list【路径】
     */
    private Map<String, List<Path>> configFileTypePathsMapping = new HashMap<>(512);
    private Map<String, List<Path>> ruleSuffixFilePathMap = new HashMap<>(512);
    private Map<String, List<Rule>> ruleSuffixMap = new HashMap<>(512);

    /**
     * 规则关联的技术
     * key:技术id
     * value:Technology
     */
    private Map<String, Technology> technologyHashMap = new HashMap<>(64);

    @Autowired
    public Inspector(BudgetUitl budgetUitl, RuleService ruleService, TechnologyDao technologyDao, ParameterDao parameterDao, AnalysisFile analysisFile) {
        this.budgetUitl = budgetUitl;
        this.ruleService = ruleService;
        this.technologyDao = technologyDao;
        this.parameterDao = parameterDao;
        this.analysisFile = analysisFile;
    }

    /**
     * 评估
     *
     * @return 报告
     */
    public ReportVo inspect() {
        //初始化成员变量
        initData();
        //扫描文件，进行文件分类
        HashMap<String, String> map = new HashMap<>();
        map.put("msg","开始扫描文件");
        asyncTask.sendMessageToAll(map);
        scanFiles();
        map.put("msg","文件扫描结束");
        asyncTask.sendMessageToAll(map);
        //配置参数
        inspectParameter.setCodeSize((int) codeSize);
        report.setFileNum(fileNum);
        report.setFileLine(fileLine);
        log.info("inspect:源代码扫描完成,统计各个文件后缀完成");
        //统计项目语言
        setReportLanguageAndFrame();
        //根据扫描结果以及用户配置得出需要使用的规则及技术
        ruleTransform(inspectParameter.getRecastMethod());
        //扫描源代码文件
        map.put("msg","开始扫描源代码文件");
        asyncTask.sendMessageToAll(map);
        forEachFilesMap();
        map.put("msg","源代码文件扫描结束");
        asyncTask.sendMessageToAll(map);
        //将得到的告警信息根据技术id进行转换
        Set<String> technologyIds = warns.stream().map(Warn::getTechnologyId).collect(Collectors.toSet());
        List<Technology> technologies = technologyDao.findAllById(technologyIds);

        //计算技术金额
        Integer technologyFund = 0;
        for (Technology tech : technologies) {
            technologyFund += tech.getFund();
        }
        //计算预算
        if (inspectParameter.getValid() != null) {
            BudgetVo budget = budgetUitl.getBudget(technologyFund, report, inspectParameter);
            report.setBudgets(budget);
        }
        parameterDao.save(inspectParameter);
        //填充地址(如果有)
        report.setGitAddress(inspectParameter.getGitAddress());
        //填充适配技术
        report.setTechnologies(technologies);
        //填充依赖
        report.setDependencyVo(dependencyVo);
        //数据转换
        HashMap<String, List<Warn>> warnMap = getWarnMap();
        ruleService.addListRulePlus(rules);
        report.setWarnDetails(warnMap);
        log.info("评估报告关键技术,{}", warnMap);
        return report;
    }

    /**
     * 遍历操作扫描文件后得出的文件
     */
    private void forEachFilesMap() {
        //解析配置文件集合
        for (Map.Entry<String, List<Path>> entry : configFileTypePathsMapping.entrySet()) {
            /**
             * 配置文件的一个类型，xml文件
             */
            if ("xml".equals(entry.getKey())) {


                for (Path path : entry.getValue()) {
                    if (path.getFileName().endsWith("pom.xml")) {
                        try {
                            // TODO: 2020-02-28 解析maven树文件，设置依赖保存到redis
                            report.setManager(DependenceManagement.MAVEN.getStatus());
                            ProjectPom projectPom = analysisFile.analysisPom(path);
                            StringBuilder stringBuilder = new StringBuilder();
                            for (PomDependency dependency : projectPom.getDependencies()) {
                                setRule(stringBuilder, dependency);
                            }
                            List<String> lines;
                            lines = Files.readAllLines(path);
                            for (int i = 0; i < lines.size(); i++) {
                                valiWarn(rules, path, lines.get(i), i + 1);
                            }
                            dependencyVo.add(projectPom);
                        } catch (IOException e) {
                            log.error("路径文件读取错误{}", path.toString());
                        }
                    }
                }
            }
        }
        HashMap<String, String> map = new HashMap<>();
        //指定后缀到文件匹配关键字
        int fileNum = 0;
        int fileSize = 0;
        List<Integer> collect = ruleSuffixFilePathMap.entrySet().stream().map(Map.Entry::getValue).map(List::size).collect(Collectors.toList());
        for (Integer integer : collect) {
            fileSize = fileSize + integer;
        }
        for (Map.Entry<String, List<Path>> entry : ruleSuffixFilePathMap.entrySet()) {
            //entry，key为后缀，value为文件列表
            String key = entry.getKey();
            //从ruleSuffixList获取指定后缀的规则列表
            List<Rule> rules = ruleSuffixMap.get(key);
            for (Path path1 : entry.getValue()) {
                try {
                    fileNum = fileNum + 1;
                    map.put("msg", fileNum + "/" + fileSize);
                    System.out.println(fileNum + "/" + fileSize);
                    asyncTask.sendMessageToAll(map);
                    //如果文件类型为jar活着class则不读取
                    if (path1.toAbsolutePath().toString().endsWith("jar") || path1.toAbsolutePath().toString().endsWith("class")) {
                        continue;
                    }
                    valiWarn(rules, path1, path1.getFileName().toString(), 0);
                    //将文件的每一行都与规则匹配
                    List<String> strings = Files.readAllLines(path1);
                    for (int i = 0; i < strings.size(); i++) {
                        valiWarn(rules, path1, strings.get(i), i + 1);
                    }
                } catch (IOException e) {
                    log.error("解析{}出错,异常信息为{}", path1.toAbsolutePath().toString(), e.getMessage());
                }

            }
        }
    }


    /**
     * 比对源文件数量得出语言架构
     */
    private void setReportLanguageAndFrame() {
        HashSet<LanguageEnum> languageEnums = new HashSet<>();
        String most = null;
        int mostStatus = 0;
        int maxnum = 0;
        //统计文件最多的
        for (Map.Entry<String, Counter> entry : languageMatchMap.entrySet()) {
            if (entry.getValue().getNumber() > maxnum) {
                LanguageEnum language = suffixLanguageMapping.get(entry.getKey());
                most = language.name();
                mostStatus = language.getCode();
                maxnum = entry.getValue().getNumber();
            }
            if (entry.getValue().getNumber() > 0) {
                LanguageEnum language = suffixLanguageMapping.get(entry.getKey());
                languageEnums.add(language);
            }
        }
        //设置是否需要重构
        List<LanguageEnum> newLanguageEnums = languageEnums.stream()
                .filter(e -> e.getCode() != 4 & e.getCode() != 5 & e.getCode() != 6)
                .collect(Collectors.toList());
        if (!newLanguageEnums.isEmpty()) {
            report.setRecastMethod(RecastMethod.RECONSITUTION.getStatus());
            log.info("inspect:代码解析完成,建议进行适配重构");
        } else {
            List<LanguageEnum> enums = languageEnums.stream()
                    .filter(e -> e.getCode() != 4 & e.getCode() != 6)
                    .collect(Collectors.toList());
            if(enums.isEmpty()){
                report.setRecastMethod(RecastMethod.MODIFICATION.getStatus());
                log.info("inspect:代码解析完成,无需进行代码修改");
            }else{
                report.setRecastMethod(RecastMethod.NOMODIFICATION.getStatus());
                log.info("inspect:代码解析完成,建议进行代码修改");
            }
        }

        List<Integer> languageCodes = languageEnums.stream()
                .map(LanguageEnum::getCode)
                .collect(Collectors.toList());
        //设置语言
        report.setLanguage(most == null ? LanguageEnum.NONE.getCode() : mostStatus);
        report.setLanguages(languageCodes);
        //设置架构
        report.setFramework(languageMatchMap.get("jsp").i > 0 ? Framework.MIXTURE.getStatus() : Framework.SEPARATE.getStatus());
    }

    /**
     * rule所需要数据装配
     *
     * @param status 状态
     *               1：改造
     *               2：适配
     */
    private void ruleTransform(Integer status) {
        //如果需要改造则查询所有规则
        if (status == 1) {
            this.ruleList = ruleService.findRules();
        } else {
            //只查询前端技术
            List<Technology> front = technologyDao.findAllByBackorfrontEquals(1);
            List<String> technologyIds = front.stream().map(Technology::getId).collect(Collectors.toList());
            this.ruleList = ruleService.findAllByTechnologyIdIn(technologyIds);
        }
        //得到目前适用的技术id
        Set<String> technologyIds = ruleList.stream().map(Rule::getTechnologyId).collect(Collectors.toSet());

        List<Technology> technologies = technologyDao.findAllByIdIn(new ArrayList<>(technologyIds));
        for (Technology technology : technologies) {
            technologyHashMap.put(technology.getId(), technology);
        }
        //根据后缀名进行规则收集
        for (Rule rule : ruleList) {
            if (!ruleSuffixMap.containsKey(rule.getSuffix())) {
                ruleSuffixMap.put(rule.getSuffix(), new ArrayList<>());
                ruleSuffixMap.get(rule.getSuffix()).add(rule);
            } else {
                ruleSuffixMap.get(rule.getSuffix()).add(rule);
            }
        }
        ArrayList<String> ruleSuffixKeys = new ArrayList<>();
        for (String suffix : ruleSuffixFilePathMap.keySet()) {
            if (!ruleSuffixMap.containsKey(suffix)) {
                ruleSuffixKeys.add(suffix);
            }
        }
        for (String suffix : ruleSuffixKeys) {
            ruleSuffixFilePathMap.remove(suffix);
        }
    }

    /**
     * 数据清空
     */
    private void initData() {
        //查询技术，构造支持与非支持技术对象，3个对象
        //配置语言 map结构
        codeSize = 0;
        fileLine = 0;
        fileNum = 0;
        dependencyVo = new DependencyVo();
        ruleSuffixFilePathMap.clear();
        ruleSuffixMap.clear();
        technologyHashMap.clear();
        supportWarns.clear();
        warns.clear();
        rules.clear();
        //配置 config文件 结构
        statisticsConfigFile();
        initTechnology();
        statisticsLanguage();
        findExistTechnology();
        initRule();
    }

    private void statisticsConfigFile() {
        this.configFileTypePathsMapping.clear();
        configFileTypePathsMapping.put("xml", new ArrayList<>());
        configFileTypePathsMapping.put("json", new ArrayList<>());
        configFileTypePathsMapping.put("gradle", new ArrayList<>());
        configFileTypePathsMapping.put("properties", new ArrayList<>());
        configFileTypePathsMapping.put("yml", new ArrayList<>());
    }

    private void statisticsLanguage() {
        for (String languageName : suffixLanguageMapping.keySet()) {
            //配置如：java，0
            languageMatchMap.put(languageName, new Counter());
        }
    }

    /**
     * 查询三个默认依赖
     */
    private void findExistTechnology() {
        techJavaSupport = technologyDao.findAllByTechnologyNameEquals("内部依赖(支持)");
        techNotCnSupport = technologyDao.findAllByTechnologyNameEquals("内部依赖(不支持)");
        techUnKnowSupport = technologyDao.findAllByTechnologyNameEquals("内部依赖(未知)");
    }

    /**
     * 初始化规则
     */
    private void initRule() {
        //查询所有规则，第一遍扫描文件需要
        this.ruleList = ruleService.findRules();
        //根据后缀名，收集文件进行操作
        for (Rule rule : ruleList) {
            if (!ruleSuffixFilePathMap.containsKey(rule.getSuffix())) {
                ruleSuffixFilePathMap.put(rule.getSuffix(), new ArrayList<>());
            }
            if (!ruleMap.containsKey(rule.getTarget() + ":" + rule.getSuffix())) {
                ruleMap.put(rule.getTarget() + ":" + rule.getSuffix(), rule);
            }
        }
    }

    /**
     * 初始化技术
     */
    private void initTechnology() {
        //查询所有技术，第一遍扫描文件需要
        List<Technology> technologies = technologyDao.findAll();
        for (Technology technology : technologies) {
            technologyHashMap.put(technology.getId(), technology);
        }
    }

    /**
     * 数据转换
     *
     * @return map key=技术名称 value=匹配点
     */
    private HashMap<String, List<Warn>> getWarnMap() {
        List<Technology> technologies = technologyDao.findAll();

        HashMap<String, Technology> techMap = new HashMap<>();
        for (Technology technology : technologies) {
            if (!techMap.containsKey(technology.getId())) {
                techMap.put(technology.getId(), technology);
            }
        }
        HashMap<String, List<Warn>> warnMap = new HashMap<>();
        warns.addAll(supportWarns);
        for (Warn warn : warns) {
            if (!warnMap.containsKey(warn.getTechnologyName())) {
                ArrayList<Warn> warns1 = new ArrayList<>();
                warns1.add(warn);
                warnMap.put(techMap.get(warn.getTechnologyId()).getTechnologyName(), warns1);
            } else {
                warnMap.get(warn.getTechnologyName()).add(warn);
            }
        }
        for (Technology technology : technologies) {
            if (!warnMap.containsKey(technology.getTechnologyName())) {
                ArrayList<Warn> warns1 = new ArrayList<>();
                warnMap.put(technology.getTechnologyName(), warns1);
            }
        }
        return warnMap;
    }

    /**
     * @param stringBuilder string缓冲区
     * @param dependency    依赖
     */
    private void setRule(StringBuilder stringBuilder, PomDependency dependency) {

        stringBuilder.append(dependency.getGroupId()).append(":").append(dependency.getArtifactId());
        Rule rule = new Rule();
        if (inspectParameter.getAdmin() == 1) {
            rule.setTechnologyId(techJavaSupport.getId());
            rule.setTechnologyName(techJavaSupport.getTechnologyName());
            dependency.setSupport(1);
        } else {
            rule.setTechnologyId(techUnKnowSupport.getId());
            rule.setTechnologyName(techUnKnowSupport.getTechnologyName());
        }
        rule.setTarget(dependency.getGroupId());
        rule.setSuffix("*");
        rule.setId(UUIDUtil.getUUID());

        Rule rule1 = new Rule();
        rule1.setTechnologyId(techJavaSupport.getId());
        rule1.setTarget(dependency.getArtifactId());
        rule1.setTechnologyName(techJavaSupport.getTechnologyName());
        rule1.setSuffix("*");
        rule1.setId(UUIDUtil.getUUID());
        if (!ruleMap.containsKey(dependency.getGroupId() + ":" + rule.getSuffix())) {
            rules.add(rule);
            ruleMap.put(dependency.getGroupId() + ":" + rule.getSuffix(), rule);
        }
        if (!ruleMap.containsKey(dependency.getArtifactId() + ":" + rule1.getSuffix())) {
            rules.add(rule1);
            ruleMap.put(dependency.getArtifactId() + ":" + rule1.getSuffix(), rule1);
        }
        stringBuilder.delete(0, stringBuilder.length());
    }


    /**
     * 将数据与规则进行匹配，得出当前依赖是否支持
     *
     * @param rules 规则
     * @param path  匹配的文件
     * @param data  行数据
     * @param num   行数
     * @return 1：正常、2：不支持依赖
     */
    public int valiWarn(List<Rule> rules, Path path, String data, int num) {
        //默认未知依赖
        int supportStatus = 3;
        for (Rule rule : rules) {
            //规则目标字符串为null 或者 空时
            if (rule.getTarget() == null || rule.getTarget().isEmpty()) {
                continue;
            }
            //匹配字符串
            int index = KmpUtil.kmpMatch(data, rule.getTarget());
            //当index>0时代表data中有当前规则
            if (index > -1) {
                //判断当前规则是否是不支持规则
                Warn warn = new Warn();
                //如果是不支持规则则需要保存匹配信息
                warn.setFilePath(path.toAbsolutePath().toString());
                warn.setLineNum(num);
                warn.setRuleId(rule.getId());
                warn.setRule(rule.getTarget());
                warn.setTechnologyId(rule.getTechnologyId());
                Technology technology = technologyHashMap.get(rule.getTechnologyId());
                warn.setTechnologyName(technology.getTechnologyName());
                warn.setCategoryId(technology.getCategory_id());
                if (technologyHashMap.get(rule.getTechnologyId()).getSupport() != 1) {
                    warns.add(warn);
                    //设置a=0代表当前依赖有问题
                    supportStatus = technology.getSupport();
                } else {
                    if (supportWarns.size() != 10) {
                        supportWarns.add(warn);
                    }
                    Integer supportSize = report.getSupportSize();
                    int i = supportSize + 1;
                    report.setSupportSize(i);
                    supportStatus = 1;
                }
            }
        }
        return supportStatus;
    }

    /**
     * 扫描源文件
     *
     * @throws IOException
     */
    public void scanFiles() {
        //以下为计算文件名称匹配正则表达式
        FileSystem aDefault = FileSystems.getDefault();
        Map<String, PathMatcher> languageSuffixMatcherMapping = new HashMap<>(16);
        //构造各个语言后缀文件的正则表达式
        for (String s : suffixLanguageMapping.keySet()) {
            languageSuffixMatcherMapping.put(s, aDefault.getPathMatcher("glob:**/*." + s));
        }
        //构造各个配置文件的正则表达式，用于解析依赖
        Map<PathMatcher, String> configFileMatcherSuffixMapping = new HashMap<>(16);
        for (String s : configFileTypePathsMapping.keySet()) {
            configFileMatcherSuffixMapping.put(aDefault.getPathMatcher("glob:**/*." + s), s);
        }
        //构造规则后缀的正则表达式
        Map<PathMatcher, String> ruleSuffixMap = new HashMap<>(16);
        for (String s : ruleSuffixFilePathMap.keySet()) {
            ruleSuffixMap.put(aDefault.getPathMatcher("glob:**/*." + s), s);
        }
        //文件读取

        if (inspectParameter.getSourceAddress() != null) {
            try {
                Files.walkFileTree(Paths.get(inspectParameter.getSourceAddress()), new FileVisitor<Path>() {
                    @Override
                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {

                        return FileVisitResult.CONTINUE;
                    }

                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                        //扫描jar文件时
                        if (file.getFileName().toString().endsWith(".jar")) {
                            //新建一个pom对象
                            ProjectPom projectPom = new ProjectPom();
                            //截取jar名称
                            String patten = RegexUtil.patten(file.getFileName().toString());
                            //新建一个依赖对象
                            PomDependency pomDependency = new PomDependency();
                            pomDependency.setArtifactId(patten);
                            projectPom.getDependencies().add(pomDependency);
                            //当参数为1时代表上传者管理员，代码可绝对信任，将jar名称当作可支持依赖添加进规则库中
                            if (inspectParameter.getAdmin() == 1) {
                                //新建规则对象
                                Rule rule = new Rule();
                                //设置适配技术id
                                rule.setTechnologyId(techJavaSupport.getId());
                                rule.setTarget(patten);
                                //设置文件后缀
                                rule.setSuffix("*");
                                rule.setId(UUIDUtil.getUUID());
                                rule.setTechnologyName(techJavaSupport.getTechnologyName());
                                //做规则查询，不用去数据库查询

                                if (!ruleMap.containsKey(patten + ":" + rule.getSuffix())) {
                                    rules.add(rule);
                                    ruleMap.put(patten + ":" + rule.getSuffix(), rule);
                                }
                                //设置当前依赖为可支持
                                pomDependency.setSupport(1);
                            } else {
                                //为普通用户上传，依赖需要检查是否支持。
                                int i = valiWarn(ruleList, file, patten, 0);
                                //如果值为0则代表是有不支持技术到匹配
                                pomDependency.setSupport(i);
                            }
                            dependencyVo.add(projectPom);
                        }
                        fileNum += 1;
                        try {
                            List<String> allLines = Files.readAllLines(file);
                            fileLine += allLines.size();
                        } catch (IOException e) {
                            log.error("当前文件无法读取:{}", e.getMessage());
                        }

                        for (Map.Entry<String, PathMatcher> entry : languageSuffixMatcherMapping.entrySet()) {
                            //通过正则表达式匹配.java类型后缀文件，并+1
                            if (entry.getValue().matches(file)) {
                                long length = file.toFile().length();
                                codeSize += length / 1024;
                                languageMatchMap.get(entry.getKey()).plus();
                            }
                        }
                        for (Map.Entry<PathMatcher, String> entry : configFileMatcherSuffixMapping.entrySet()) {
                            //通过配置文件正则表达式匹配.xml文件，记录文件地址
                            if (entry.getKey().matches(file)) {
                                configFileTypePathsMapping.get(entry.getValue()).add(file);
                            }
                        }
                        for (Map.Entry<PathMatcher, String> entry : ruleSuffixMap.entrySet()) {
                            //通过规则匹配后缀正则表达式匹配,记录匹配上的文件地址
                            if (entry.getKey().matches(file)) {
                                ruleSuffixFilePathMap.get(entry.getValue()).add(file);
                            }
                        }
                        return FileVisitResult.CONTINUE;
                    }

                    @Override
                    public FileVisitResult visitFileFailed(Path file, IOException exc) {
                        return FileVisitResult.CONTINUE;
                    }

                    @Override
                    public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
                        return FileVisitResult.CONTINUE;
                    }
                });
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public class Counter {
        private int i = 0;

        void plus() {
            i++;
        }

        int getNumber() {
            return i;
        }
    }
}
