package com.yonge.netty.server.service;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.sound.sampled.AudioFormat;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.yonge.audio.analysis.AudioFloatConverter;
import com.yonge.audio.analysis.detector.YINPitchDetector;
import com.yonge.audio.utils.ArrayUtil;
import com.yonge.netty.dto.NoteFrequencyRange;
import com.yonge.netty.dto.WebSocketResponse;
import com.yonge.netty.server.handler.NettyChannelManager;
import com.yonge.netty.server.handler.message.MessageHandler;
import com.yonge.netty.server.processor.WaveformWriter;

import io.netty.channel.Channel;

@Service
public class DelayCheckHandler implements MessageHandler {

	private final static Logger LOGGER = LoggerFactory.getLogger(DelayCheckHandler.class);

	private int standardFrequecy = 3000;

	/**
	 * @describe 采样率
	 */
	private float sampleRate = 44100;

	/**
	 * 每个采样大小（Bit）
	 */
	private int bitsPerSample = 16;

	/**
	 * 通道数
	 */
	private int channels = 1;
	
	private int bufferSize = 1024 * 1;

	private boolean signed = true;

	private boolean bigEndian = false;

	private AudioFormat audioFormat = new AudioFormat(sampleRate, bitsPerSample, channels, signed, bigEndian);

	private AudioFloatConverter converter = AudioFloatConverter.getConverter(audioFormat);
	
	private boolean isRecWav = false;

	private ConcurrentMap<Channel, UserContext> userABCMap = new ConcurrentHashMap<Channel, UserContext>();

	private String tmpFileDir = "/mdata/soundCompare/";

	private SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmSS");

	@Autowired
	private NettyChannelManager nettyChannelManager;

	@Override
	public String getAction() {
		return "DELAY_CHECK";
	}

	@Override
	public boolean handleTextMessage(String userId, Channel channel, String jsonMsg) {

		String command = (String) JSONPath.extract(jsonMsg, "$.header.commond");

		UserContext userContext = null;

		switch (command) {
		case "recordStart":

			userContext = new UserContext(0, false);

			userABCMap.put(channel, userContext);
			
			JSONObject dataObj = JSONObject.parseObject(JSON.toJSONString(JSONPath.extract(jsonMsg, "$.body")));
			
			if(dataObj.get("HZ") != null) {
				String hzStr = dataObj.get("HZ").toString();
				standardFrequecy = Integer.parseInt(hzStr);
			}

			break;
		case "recordEnd":

			userContext = userABCMap.get(channel);

			if (userContext == null) {
				userContext = new UserContext(0, false);
			}

			WaveformWriter waveFileProcessor = userContext.getWaveformWriter();
			if (waveFileProcessor != null) {
				// 写文件头
				waveFileProcessor.processingFinished();
			}

			userContext = userABCMap.get(channel);

			if (userContext == null) {
				userContext = new UserContext(0, false);
			}

			Map<String, Object> params = new HashMap<String, Object>();
			params.put("firstNoteDelayDuration", userContext.getDelayDuration());

			WebSocketResponse<Map<String, Object>> resp = new WebSocketResponse<Map<String, Object>>(getAction(), command, params);

			nettyChannelManager.sendTextMessage(userId, resp);

			userContext.setDelayDuration(0);
			userContext.setIsOver(false);
			userContext.setWaveformWriter(null);
			userContext.setChannelBufferBytes(new byte[0]);
			userABCMap.put(channel, userContext);
			break;

		default:
			break;
		}

		return true;
	}

	@Override
	public boolean handleBinaryMessage(String userId, Channel channel, byte[] bytes) {

		UserContext userContext = userABCMap.get(channel);

		if (userContext == null) {
			userContext = new UserContext(0, false);
		}

		// 写录音文件
		if (isRecWav) {
			WaveformWriter waveFileProcessor = userContext.getWaveformWriter();
			if (waveFileProcessor == null) {
				File file = new File(tmpFileDir + userId + "_CHECK_" + sdf.format(new Date()) + ".wav");
				waveFileProcessor = new WaveformWriter(file.getAbsolutePath());
				userContext.setWaveformWriter(waveFileProcessor);

				userABCMap.put(channel, userContext);
			}
			waveFileProcessor.process(bytes);
		}

		if (userContext.isOver) {
			return true;
		}
		
		userContext.setChannelBufferBytes(ArrayUtil.mergeByte(userContext.getChannelBufferBytes(), bytes));

		int totalLength = userContext.getChannelBufferBytes().length;
		
		while (totalLength >= bufferSize) {
			byte[] bufferData = ArrayUtil.extractByte(userContext.getChannelBufferBytes(), 0, bufferSize - 1);

			if (bufferSize != totalLength) {
				userContext.setChannelBufferBytes(ArrayUtil.extractByte(userContext.getChannelBufferBytes(), bufferSize, totalLength - 1));
			} else {
				userContext.setChannelBufferBytes(new byte[0]);
			}

			float[] sampleFloats = new float[bufferSize / 2];

			converter.toFloatArray(bufferData, sampleFloats);
			
			YINPitchDetector frequencyDetector = new YINPitchDetector(sampleFloats.length, audioFormat.getSampleRate());

			int playFrequency = (int) frequencyDetector.getFrequency(sampleFloats);

			// int amplitude = (int) Signals.decibels(samples);

			double durationTime = 1000 * (sampleFloats.length * 2) / audioFormat.getSampleRate() / (audioFormat.getSampleSizeInBits() / 8);

			double playTime = userContext.delayDuration;

			playTime += durationTime;

			LOGGER.info("DurationTime:{}	 playFrequency:{}  PlayTime:{}" ,durationTime,playFrequency,playTime);
			
			NoteFrequencyRange nfr = new NoteFrequencyRange(440, playFrequency);
			
			if (nfr.getMinFrequency() < standardFrequecy && nfr.getMaxFrequency() > standardFrequecy) {

				userContext.setIsOver(true);
				userABCMap.put(channel, userContext);
				return true;
			}

			userContext.setDelayDuration(playTime);

			totalLength = userContext.getChannelBufferBytes().length;
		}

		return true;
	}

	public ConcurrentMap<Channel, UserContext> getUserABCMap() {
		return userABCMap;
	}

	class UserContext {

		public UserContext(double delayDuration, boolean isOver) {
			this.delayDuration = delayDuration;
			this.isOver = isOver;
		}

		private double delayDuration;

		private boolean isOver;
		
		private byte[] channelBufferBytes = new byte[0];

		private WaveformWriter waveformWriter;

		public double getDelayDuration() {
			return delayDuration;
		}

		public void setDelayDuration(double delayDuration) {
			this.delayDuration = delayDuration;
		}

		public boolean isOver() {
			return isOver;
		}

		public void setIsOver(boolean isOver) {
			this.isOver = isOver;
		}

		public byte[] getChannelBufferBytes() {
			return channelBufferBytes;
		}

		public void setChannelBufferBytes(byte[] channelBufferBytes) {
			this.channelBufferBytes = channelBufferBytes;
		}

		public WaveformWriter getWaveformWriter() {
			return waveformWriter;
		}

		public void setWaveformWriter(WaveformWriter waveformWriter) {
			this.waveformWriter = waveformWriter;
		}
	}

}
