package cc.winboll.studio.midiplayer;

/**
 * @Author ZhanGSKen<zhangsken@qq.com>
 * @Date 2025/06/29 10:13
 * @Describe MidiPlayer
 */
import android.content.Context;
import android.media.midi.MidiDevice;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiInputPort;
import android.media.midi.MidiManager;
import android.os.Build;
import android.os.Handler;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import cc.winboll.studio.libappbase.LogUtils;

/**
 * MIDI播放器核心类，支持加载MIDI文件、连接合成器、控制播放轨道及事件解析
 * 需Android 6.0（API 23）及以上版本
 */
public class MidiPlayer {

    public static final String TAG = "MidiPlayer";

    // 上下文与系统服务
    private final Context mContext;
    private MidiManager mMidiManager;

    // MIDI设备与端口
    private MidiDevice mMidiDevice;
    private MidiInputPort mInputPort;
    private boolean isSynthConnected = false;

    // 线程与Handler
    private ExecutorService mExecutor;
    private final Handler mHandler = new Handler();

    // 播放数据与状态
    private MidiTrack[] mTracks;
    private boolean isPlaying = false;
    private int mCurrentTrack = -1; // -1表示播放所有轨道
    private int mTicksPerBeat; // 每拍的ticks数（从MIDI文件解析）
    private long startTime; // 播放开始时间（用于日志）

    // 音色库管理
    private final SoundFontManager mSoundFontManager;

    // 连接回调接口
    public interface OnSynthConnectedListener {
        void onConnected(boolean success);
    }
    private OnSynthConnectedListener mConnectionListener;


    /**
     * MIDI事件类，存储事件数据及时间间隔
     */
    public static class MidiEvent {
        public byte[] data; // 事件指令数据
        public int deltaTicks; // 与上一事件的时间间隔（ticks）

        public MidiEvent(byte[] data, int deltaTicks) {
            this.data = data;
            this.deltaTicks = deltaTicks;
        }
    }


    /**
     * MIDI轨道类，包含事件列表及播放状态
     */
    public static class MidiTrack {
        private final List<MidiEvent> events;
        private boolean isMuted = false;
        private int currentIndex = 0;

        public MidiTrack(List<MidiEvent> events) {
            this.events = events;
        }

        public boolean hasNextEvent() {
            return currentIndex < events.size();
        }

        public MidiEvent nextEvent() {
            return currentIndex < events.size() ? events.get(currentIndex++) : null;
        }

        public void reset() {
            currentIndex = 0;
        }

        public void setMute(boolean mute) {
            isMuted = mute;
        }

        public boolean isMuted() {
            return isMuted;
        }
    }


    /**
     * MIDI解析结果类，包含轨道数组与每拍ticks数
     */
    public static class MidiParseResult {
        public MidiTrack[] tracks;
        public int ticksPerBeat;

        public MidiParseResult(MidiTrack[] tracks, int ticksPerBeat) {
            this.tracks = tracks;
            this.ticksPerBeat = ticksPerBeat;
        }
    }


    // 构造方法
    public MidiPlayer(Context context) {
        mContext = context;
        mSoundFontManager = new SoundFontManager(context);
        // 初始化MIDI服务（需API 23+）
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            mMidiManager = (MidiManager) context.getSystemService(Context.MIDI_SERVICE);
        }
    }


    // 设置合成器连接回调
    public void setOnSynthConnectedListener(OnSynthConnectedListener listener) {
        mConnectionListener = listener;
    }


    /**
     * 加载MIDI文件并解析为轨道
     * @param file MIDI文件
     * @return 是否加载成功
     */
    public boolean loadMidiFile(File file) {
        try {
            MidiParser parser = new MidiParser(file);
            MidiParseResult result = parser.parseWithTicks();
            mTracks = result.tracks;
            mTicksPerBeat = result.ticksPerBeat;
            return mTracks != null && mTracks.length > 0;
        } catch (IOException e) {
            LogUtils.d(TAG, "加载MIDI文件失败: " + e.getMessage());
            return false;
        }
    }


    /**
     * 加载音色库文件
     * @param soundFontFile 音色库文件
     * @return 是否加载成功
     */
    public boolean loadSoundFont(File soundFontFile) {
        return mSoundFontManager.loadSoundFont(soundFontFile);
    }


    /**
     * 开始播放MIDI文件
     */
    public void start() {
        LogUtils.d(TAG, "start()");
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            LogUtils.d(TAG, "需Android 6.0及以上版本");
            notifyConnectionResult(false);
            return;
        }
        if (isPlaying || mTracks == null || mMidiManager == null) {
            return;
        }

        isPlaying = true;
        getExecutor().execute(new Runnable() {
				@Override
				public void run() {
					LogUtils.d(TAG, "开始连接合成器");
					isSynthConnected = false;
					connectToSynth();

					// 等待合成器连接（最多3秒）
					int waitCount = 0;
					while (!isSynthConnected && waitCount < 30) {
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							Thread.currentThread().interrupt();
							break;
						}
						waitCount++;
					}

					if (!isSynthConnected || mInputPort == null) {
						LogUtils.d(TAG, "合成器连接失败");
						isPlaying = false;
						notifyConnectionResult(false);
						return;
					}

					notifyConnectionResult(true);
					playTracksWithSpeedControl();
				}
			});
    }


    /**
     * 带速度控制的轨道播放逻辑
     */
    private void playTracksWithSpeedControl() {
        LogUtils.d(TAG, "开始播放，每拍ticks数: " + mTicksPerBeat);
        if (mInputPort == null || mTracks == null || mTicksPerBeat <= 0) {
            return;
        }

        startTime = System.currentTimeMillis();
        int trackCount = mTracks.length;
        long[] trackNextEventTime = new long[trackCount]; // 各轨道下一事件的播放时间（ms）

        // 初始化轨道状态
        for (int i = 0; i < trackCount; i++) {
            mTracks[i].reset();
            trackNextEventTime[i] = 0;
        }

        // 默认BPM（可通过MIDI文件中的tempo事件动态调整）
        int bpm = 120;
        double msPerTick = (60.0 / bpm) * 1000 / mTicksPerBeat;

        // 循环播放事件
        while (isPlaying) {
            // 找到最早需要播放的事件时间
            long earliestTime = Long.MAX_VALUE;
            for (int i = 0; i < trackCount; i++) {
                if ((mCurrentTrack == -1 || mCurrentTrack == i) 
                    && !mTracks[i].isMuted() 
                    && mTracks[i].hasNextEvent()) {
                    earliestTime = Math.min(earliestTime, trackNextEventTime[i]);
                }
            }

            if (earliestTime == Long.MAX_VALUE) {
                LogUtils.d(TAG, "所有轨道事件播放完毕");
                break;
            }

            // 计算等待时间并休眠
            long currentTime = System.currentTimeMillis() - startTime;
            long delay = earliestTime - currentTime;
            if (delay > 0) {
                try {
                    Thread.sleep(delay);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }

            // 播放所有到达时间点的事件
            for (int i = 0; i < trackCount; i++) {
                if ((mCurrentTrack == -1 || mCurrentTrack == i) 
                    && !mTracks[i].isMuted() 
                    && mTracks[i].hasNextEvent() 
                    && trackNextEventTime[i] == earliestTime) {

                    MidiEvent event = mTracks[i].nextEvent();
                    if (event != null) {
                        try {
                            logMidiEventDetails(i, event);
                            mInputPort.send(event.data, 0, event.data.length);
                            // 更新下一事件时间
                            trackNextEventTime[i] = earliestTime + (long) (event.deltaTicks * msPerTick);
                        } catch (IOException e) {
                            LogUtils.d(TAG, "发送事件失败: " + e.getMessage());
                        }
                    }
                }
            }
        }

        isPlaying = false;
        LogUtils.d(TAG, "播放结束");
    }


    /**
     * 解析MIDI事件并输出详细日志
     */
    private void logMidiEventDetails(int trackIndex, MidiEvent event) {
        // 无效事件增加原始数据日志，便于调试
        if (event.data == null || event.data.length < 2) {
            String dataStr = (event.data != null) ? Arrays.toString(event.data) : "null";
            LogUtils.d(TAG, "轨道[" + trackIndex + "] 无效事件: 数据长度不足 (" 
					   + (event.data != null ? event.data.length : 0) + "字节)，原始数据: " + dataStr);
            return;
        }

        int statusByte = event.data[0] & 0xFF;
        int eventType = statusByte & 0xF0;
        int channel = (statusByte & 0x0F) + 1; // 通道号1-16

        StringBuilder log = new StringBuilder();
        log.append("轨道[").append(trackIndex).append("] 事件类型: ");

        switch (eventType) {
            case 0x90: // 音符开启
                if (event.data.length >= 3) {
                    int pitch = event.data[1] & 0xFF;
                    int velocity = event.data[2] & 0xFF;
                    log.append("音符开启 | 通道: ").append(channel)
                        .append(" | 音高: ").append(pitch).append(" (").append(getNoteName(pitch)).append(")")
                        .append(" | 力度: ").append(velocity)
                        .append(" | 间隔ticks: ").append(event.deltaTicks)
                        .append(" | 播放时间: ").append(System.currentTimeMillis() - startTime).append("ms");
                } else {
                    log.append("音符开启 (数据不完整) | 长度: ").append(event.data.length);
                }
                break;

            case 0x80: // 音符关闭
                if (event.data.length >= 3) {
                    int pitch = event.data[1] & 0xFF;
                    int velocity = event.data[2] & 0xFF;
                    log.append("音符关闭 | 通道: ").append(channel)
                        .append(" | 音高: ").append(pitch).append(" (").append(getNoteName(pitch)).append(")")
                        .append(" | 力度: ").append(velocity)
                        .append(" | 间隔ticks: ").append(event.deltaTicks)
                        .append(" | 播放时间: ").append(System.currentTimeMillis() - startTime).append("ms");
                } else {
                    log.append("音符关闭 (数据不完整) | 长度: ").append(event.data.length);
                }
                break;

            case 0xB0: // 控制变化
                if (event.data.length >= 3) {
                    int controlNumber = event.data[1] & 0xFF;
                    int controlValue = event.data[2] & 0xFF;
                    log.append("控制变化 | 通道: ").append(channel)
                        .append(" | 控制器: ").append(controlNumber).append(" (").append(getControlName(controlNumber)).append(")")
                        .append(" | 数值: ").append(controlValue);
                } else {
                    log.append("控制变化 (数据不完整) | 长度: ").append(event.data.length);
                }
                break;

            case 0xC0: // 程序改变（乐器切换）
                if (event.data.length >= 2) {
                    int program = event.data[1] & 0xFF;
                    // 增加乐器编号有效性校验日志
                    String instrumentName = getInstrumentName(program);
                    if (program < 0 || program >= 128) {
                        instrumentName += " (超出GM标准范围0-127)";
                    }
                    log.append("程序改变 | 通道: ").append(channel)
                        .append(" | 乐器编号: ").append(program).append(" (").append(instrumentName).append(")");
                } else {
                    log.append("程序改变 (数据不完整) | 长度: ").append(event.data.length);
                }
                break;

            case 0xD0: // 通道触后事件
                log.append("通道触后 | 通道: ").append(channel)
                    .append(" | 压力值: ").append(event.data[1] & 0xFF);
                break;

            case 0xF0: // 系统专属事件(SysEx)
                log.append("系统专属事件(SysEx) | 长度: ").append(event.data.length)
                    .append(" | 首字节: 0x").append(Integer.toHexString(event.data[0] & 0xFF));
                break;

            default:
                log.append("未知类型 (0x").append(Integer.toHexString(eventType)).append(") | 通道: ").append(channel)
                    .append("，原始数据: ").append(Arrays.toString(event.data));
                break;
        }

        LogUtils.d(TAG, log.toString());
    }


    /**
     * 音高转音符名称（如60→C4）
     */
    private String getNoteName(int pitch) {
        if (pitch < 0 || pitch > 127) return "无效音高";
        String[] noteNames = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
        int octave = (pitch / 12) - 1; // C4对应60
        return noteNames[pitch % 12] + octave;
    }


    /**
     * MIDI控制器编号转名称
     */
    private String getControlName(int controlNumber) {
        switch (controlNumber) {
            case 0: return "Bank Select (MSB)";
            case 1: return "Modulation Wheel";
            case 7: return "音量控制";
            case 10: return "声像控制";
            case 11: return "表达控制";
            case 64: return "延音踏板";
            case 121: return "重置所有控制器";
            default: return "未知控制器";
        }
    }


    /**
     * 乐器编号转名称（GM标准）
     */
    private String getInstrumentName(int program) {
        String[] instruments = {
            "钢琴", "明亮钢琴", "电钢琴", "Honky-tonk钢琴", "电钢琴", "羽管键琴", 
            "击弦古钢琴", "颤音琴", "竖琴", "管风琴", "手风琴", "acoustic贝斯", 
            "电贝斯(指弹)", "电贝斯(拨片)", "小提琴", "大提琴"
        };
        if (program < 0 || program >= 128) {
            return "无效编号";
        }
        if (program < instruments.length) {
            return instruments[program];
        } else {
            return "乐器 " + program;
        }
    }


    /**
     * 连接MIDI合成器
     */
    public void connectToSynth() {
        if (mMidiManager == null) return;
        closeMidiResources();
        searchSynthInDevices();
    }

    /**
     * 搜索并选择可用的MIDI设备
     */
    private void searchSynthInDevices() {
        MidiDeviceInfo[] devices = mMidiManager.getDevices();
        if (devices == null || devices.length == 0) {
            LogUtils.d(TAG, "未检测到MIDI设备");
            isSynthConnected = false;
            return;
        }

        // 优先选择合成器类型（3），其次选择有输入端口的设备
        MidiDeviceInfo targetDevice = null;
        for (MidiDeviceInfo device : devices) {
            if (device.getType() == 3) {
                targetDevice = device;
                break;
            }
            if (targetDevice == null && device.getInputPortCount() > 0) {
                targetDevice = device;
            }
        }

        if (targetDevice != null) {
            LogUtils.d(TAG, "尝试打开设备: 设备ID=" + targetDevice.getId() + "，名称=" + targetDevice.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME));
            mMidiManager.openDevice(targetDevice, new MidiManager.OnDeviceOpenedListener() {
					@Override
					public void onDeviceOpened(MidiDevice device) {
						setupMidiDevice(device);
					}
				}, mHandler);
        } else {
            LogUtils.d(TAG, "未找到可用设备（无合成器或带输入端口的设备）");
            isSynthConnected = false;
        }
    }


    /**
     * 初始化MIDI设备并打开输入端口
     */
    private void setupMidiDevice(MidiDevice device) {
        if (device == null) {
            LogUtils.d(TAG, "打开MIDI设备失败: 设备为null");
            isSynthConnected = false;
            return;
        }

        mMidiDevice = device;
        try {
            // 从设备信息中获取输入端口数量
            MidiDeviceInfo deviceInfo = device.getInfo();
            if (deviceInfo.getInputPortCount() > 0) {
                mInputPort = device.openInputPort(0);
                isSynthConnected = (mInputPort != null);
                LogUtils.d(TAG, isSynthConnected ? "成功打开输入端口（端口0）" : "输入端口为null，打开失败");
            } else {
                LogUtils.d(TAG, "设备无输入端口，无法接收MIDI事件");
                isSynthConnected = false;
            }
        } catch (Exception e) {
            LogUtils.d(TAG, "打开端口失败: " + e.getMessage());
            isSynthConnected = false;
        }
    }


    /**
     * 关闭MIDI设备及端口资源
     */
    private void closeMidiResources() {
        if (mInputPort != null) {
            try {
                mInputPort.close();
            } catch (IOException e) {
                LogUtils.d(TAG, "关闭输入端口失败: " + e.getMessage());
            }
            mInputPort = null;
        }
        if (mMidiDevice != null) {
            try {
                mMidiDevice.close();
            } catch (IOException e) {
                LogUtils.d(TAG, "关闭MIDI设备失败: " + e.getMessage());
            }
            mMidiDevice = null;
        }
    }


    /**
     * 通知合成器连接结果
     */
    private void notifyConnectionResult(final boolean success) {
        if (mConnectionListener != null) {
            mHandler.post(new Runnable() {
					@Override
					public void run() {
						mConnectionListener.onConnected(success);
					}
				});
        }
    }


    /**
     * 设置指定轨道静音状态
     * @param trackIndex 轨道索引
     * @param mute 是否静音
     */
    public void setTrackMute(int trackIndex, boolean mute) {
        if (mTracks == null) {
            LogUtils.d(TAG, "设置静音失败：未加载MIDI轨道");
            return;
        }
        if (trackIndex >= 0 && trackIndex < mTracks.length) {
            mTracks[trackIndex].setMute(mute);
            LogUtils.d(TAG, "轨道[" + trackIndex + "] 静音状态: " + (mute ? "已静音" : "正常播放"));
        } else {
            LogUtils.d(TAG, "设置静音失败：无效轨道索引 " + trackIndex + "（总轨道数：" + mTracks.length + "）");
        }
    }


    /**
     * 切换当前播放轨道（-1表示所有轨道）
     * @param trackIndex 轨道索引
     */
    public void setCurrentTrack(int trackIndex) {
        if (mTracks == null) {
            LogUtils.d(TAG, "切换轨道失败：未加载MIDI轨道");
            return;
        }
        // 校验轨道索引有效性
        if (trackIndex == -1 || (trackIndex >= 0 && trackIndex < mTracks.length)) {
            mCurrentTrack = trackIndex;
            LogUtils.d(TAG, "切换当前播放轨道: " + (trackIndex == -1 ? "所有轨道" : "轨道[" + trackIndex + "]"));
        } else {
            LogUtils.d(TAG, "切换轨道失败：无效轨道索引 " + trackIndex + "（总轨道数：" + mTracks.length + "）");
        }
    }


    /**
     * 获取轨道总数
     * @return 轨道数量
     */
    public int getTrackCount() {
        return mTracks != null ? mTracks.length : 0;
    }


    /**
     * 暂停播放
     */
    public void pause() {
        boolean wasPlaying = isPlaying;
        isPlaying = false;
        LogUtils.d(TAG, "播放暂停: " + (wasPlaying ? "已暂停当前播放" : "当前未在播放"));
    }


    /**
     * 停止播放并释放所有资源
     */
    public void stop() {
        boolean wasPlaying = isPlaying;
        isPlaying = false;
        isSynthConnected = false;
        closeMidiResources();
        if (mExecutor != null) {
            mExecutor.shutdownNow();
            mExecutor = null;
        }
        LogUtils.d(TAG, "播放停止: " + (wasPlaying ? "已终止播放并释放资源" : "当前未在播放"));
    }


    /**
     * 获取线程池（单例）
     */
    private ExecutorService getExecutor() {
        if (mExecutor == null || mExecutor.isShutdown() || mExecutor.isTerminated()) {
            mExecutor = Executors.newSingleThreadExecutor();
        }
        return mExecutor;
    }


    /**
     * 检查合成器是否已连接
     * @return 连接状态
     */
    public boolean isSynthConnected() {
        return isSynthConnected;
    }


    /**
     * 获取当前MIDI输入端口
     * @return 输入端口
     */
    public MidiInputPort getInputPort() {
        return mInputPort;
    }
}
