package com.ssj.sys.util.xunfei;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.ssj.framework.basic.utils.DateHelper;
import com.ssj.framework.core.util.OSSUtils;
import okhttp3.*;
import okio.ByteString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.URL;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

/**
 * @author yangcan
 * 类描述: 在线语音合成（流式版）API 工具类
 * 创建时间:2019/10/12 10:21
 */
public class XunFeiTTSUtils {
    private static final Logger logger = LoggerFactory.getLogger(XunFeiTTSUtils.class);
    private static final String hostUrl = "https://tts-api.xfyun.cn/v2/tts"; //http url 不支持解析 ws/wss schema
    private static final String apiKey = "79afbe7df5639af0bdc1ecf203cb1d11";
    private static final String apiSecret = "9388d53f1824cd37a4e84dad4408c2bd";
    private static final String appid = "5d9da37b";
    //private static final String text = "今日错题 阳光学业评价 数据收集整理第6课时第3题(第2小题/第3空)第4题(第2小题/第3空)第9课时第3题(第2小题/第3空)表内除法第6课时第3题(第4小题/第8空) 五三天天练 小数点移动的变化规律用2～6的乘法口诀求商第9题(第7小题/第4空) 错题涉及知识点1.方程式的应用2.数与代数";
    public static final Gson json = new Gson();

    public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {
        URL url = new URL(hostUrl);
        SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
        format.setTimeZone(TimeZone.getTimeZone("GMT"));
        String date = format.format(new Date());
        StringBuilder builder = new StringBuilder("host: ").append(url.getHost()).append("\n").//
                append("date: ").append(date).append("\n").//
                append("GET ").append(url.getPath()).append(" HTTP/1.1");
        Charset charset = Charset.forName("UTF-8");
        Mac mac = Mac.getInstance("hmacsha256");
        SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(charset), "hmacsha256");
        mac.init(spec);
        byte[] hexDigits = mac.doFinal(builder.toString().getBytes(charset));
        String sha = Base64.getEncoder().encodeToString(hexDigits);
        String authorization = String.format("hmac username=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha);
        HttpUrl httpUrl = HttpUrl.parse("https://" + url.getHost() + url.getPath()).newBuilder().//
                addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(charset))).//
                addQueryParameter("date", date).//
                addQueryParameter("host", url.getHost()).//
                build();
        return httpUrl.toString();
    }

    public static String textToVoice(String text,String vipId) throws Exception{
        // 构建鉴权url
        String authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);
        OkHttpClient client = new OkHttpClient.Builder().build();
        //将url中的 schema http://和https://分别替换为ws:// 和 wss://
        String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://");
        Request request = new Request.Builder().url(url).build();
        // 存放音频的文件/www/files/data/upload/
        String fileCode = "/xunfei/"+DateHelper.getOrderNo(4)+"_"+ vipId;
        String staticPrefix = "/www/files/data/upload";
        String fileNamePcm = staticPrefix + fileCode+".pcm";
        String fileNameWav = fileCode+".wav";
        File f = new File(fileNamePcm);
        if (!f.exists()) {
            f.createNewFile();
        }
        FileOutputStream os = new FileOutputStream(f);
        WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                super.onOpen(webSocket, response);
                //发送数据
                JsonObject frame = new JsonObject();
                JsonObject business = new JsonObject();
                JsonObject common = new JsonObject();
                JsonObject data = new JsonObject();
                // 填充common
                common.addProperty("app_id", appid);
                //填充business
                business.addProperty("aue", "raw");
                business.addProperty("tte", "UTF8");
                //发音人:小燕(xiaoyan),许久(aisjiuxu),小萍(aisxping),小婧(aisjinger),许小宝(aisbabyxu)
                business.addProperty("vcn", "aisjinger");
                //语速，可选值：[0-100]，默认为50
                business.addProperty("speed", 30);
                business.addProperty("pitch", 50);
                //背景音乐 0:无背景音（默认值）1:有背景音
                business.addProperty("bgs", 0);
                //填充data
                data.addProperty("status", 2);
                data.addProperty("text", Base64.getEncoder().encodeToString(text.getBytes()));
                data.addProperty("encoding", "");
                //填充frame
                frame.add("common", common);
                frame.add("business", business);
                frame.add("data", data);
                webSocket.send(frame.toString());
            }
            @Override
            public void onMessage(WebSocket webSocket, String text) {
                super.onMessage(webSocket, text);
                //处理返回数据
//                logger.debug("receive=>" + text);
                ResponseData resp = null;
                try {
                    resp = json.fromJson(text, ResponseData.class);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (resp != null) {
                    if (resp.getCode() != 0) {
//                        logger.info("error=>" + resp.getMessage() + " sid=" + resp.getSid());
                        return;
                    }
                    if (resp.getData() != null) {
                        String result = resp.getData().audio;
                        byte[] audio = Base64.getDecoder().decode(result);
                        try {
                            os.write(audio);
                            os.flush();
                        } catch (IOException e) {
                            logger.error("写入文件异常", e);
                        }
                        if (resp.getData().status == 2) {
                            // todo  resp.data.status ==2 说明数据全部返回完毕，可以关闭连接，释放资源
                            logger.info("session end ");
                            webSocket.close(1000, "");
                            try {
                                os.close();
                                //将PCM格式转换为WAV格式
                                PCM2WAV.convertAudioFiles(f.getAbsolutePath(),staticPrefix + fileNameWav);
                                InputStream in = new FileInputStream(staticPrefix + fileNameWav);
                                OSSUtils.uploadInputStream(in, fileNameWav);
                                in.close();
                            } catch (Exception e) {
                                logger.error("转文件格式异常", e);
                            }
                        }
                    }
                }
            }
            @Override
            public void onMessage(WebSocket webSocket, ByteString bytes) {
                super.onMessage(webSocket, bytes);
            }
            @Override
            public void onClosing(WebSocket webSocket, int code, String reason) {
                super.onClosing(webSocket, code, reason);
                logger.info("socket closing");
            }
            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                super.onClosed(webSocket, code, reason);
                logger.info("socket closed");
            }
            @Override
            public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                super.onFailure(webSocket, t, response);
                logger.info("connection failed");
            }
        });
        return fileNameWav;
    }

    public static class ResponseData {
        private int code;
        private String message;
        private String sid;
        private Data data;
        public int getCode() {
            return code;
        }
        public String getMessage() {
            return this.message;
        }
        public String getSid() {
            return sid;
        }
        public Data getData() {
            return data;
        }
    }
    public static class Data {
        private int status;  //标志音频是否返回结束  status=1，表示后续还有音频返回，status=2表示所有的音频已经返回
        private String audio;  //返回的音频，base64 编码
        private String ced;  // 合成进度
        private String spell;  //音频的拼音标注
    }

    public static void main(String[] args) throws Exception {
        String text = "1 主题深刻，进取心较强。2 中心思想积极向上，体现了自己对生活的热爱！3 文章层次段落清晰，结构合理，流畅连贯，自然通达。4 文章的结构简洁合理。5 段落分明，过渡自然，情趣盎然，可读性强。6 标点符号使用全部正确。";
//        String text = " 刚才你吃的东西已经被我下了天下第一奇毒……一日丧命散。哈哈哈…天下第一奇毒哪论得到你的一日丧命散，应该是我们唐家的含笑半步颠才对。哈哈哈…废话，我们一日丧命散是用7种不同的毒虫加上鹤顶红提炼七七49天，无色无味，杀人也无影无踪，我们唐家的含笑半步颠是用蜂蜜，川贝，橘梗，加上天山雪莲制成，不需冷藏也没有防腐济，除了毒性猛烈以外，味道还很好吃。";
        //String text = "今日错题 阳光学业评价 数据收集整理第6课时第3题(第2小题/第3空)第4题(第2小题/第3空)第9课时第3题(第2小题/第3空)表内除法第6课时第3题(第4小题/第8空) 五三天天练 小数点移动的变化规律用2～6的乘法口诀求商第9题(第7小题/第4空) 错题涉及知识点1.方程式的应用2.数与代数";
        logger.info("文件存放路径:"+textToVoice(text,"c32acf36-dfbd-45c5-b8f3-2dd484bb007a"));
    }
}