package cc.winboll.studio.midiplayer;

/**
 * @Author ZhanGSKen<zhangsken@qq.com>
 * @Date 2025/06/29 10:26
 * @Describe MIDI文件解析器，用于解析MIDI文件并提取轨道信息
 */
import cc.winboll.studio.libappbase.LogUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

public class MidiParser {
    public static final String TAG = "MidiParser";
    private static final Charset US_ASCII = Charset.forName("US-ASCII");

    private InputStream mInputStream;
    private int mTrackCount; // 轨道数量
    private int mTicksPerBeat; // 每拍的ticks数（从文件头解析）

    public MidiParser(File file) throws IOException {
        this.mInputStream = new FileInputStream(file);
    }

    /**
     * 解析MIDI文件，返回包含轨道和每拍ticks数的结果（支持速度控制）
     */
    public MidiPlayer.MidiParseResult parseWithTicks() throws IOException {
        try {
            // 1. 验证MIDI文件头（MThd）
            if (!verifyHeader()) {
                LogUtils.d(TAG, "不是有效的MIDI文件");
                return null;
            }

            // 2. 读取文件头信息（包含每拍ticks数）
            readHeaderInfo();

            // 3. 解析每个轨道（包含事件的deltaTicks）
            MidiPlayer.MidiTrack[] tracks = new MidiPlayer.MidiTrack[mTrackCount];
            for (int i = 0; i < mTrackCount; i++) {
                tracks[i] = parseTrackWithTicks();
            }

            // 返回解析结果（轨道数组 + 每拍ticks数）
            return new MidiPlayer.MidiParseResult(tracks, mTicksPerBeat);
        } finally {
            if (mInputStream != null) {
                mInputStream.close();
            }
        }
    }

    /**
     * 原始解析方法（兼容旧逻辑）
     */
    public MidiTrack[] parse() throws IOException {
        try {
            if (!verifyHeader()) {
                LogUtils.d(TAG, "不是有效的MIDI文件");
                return null;
            }

            readHeaderInfo();
            MidiTrack[] tracks = new MidiTrack[mTrackCount];
            for (int i = 0; i < mTrackCount; i++) {
                tracks[i] = parseTrack();
            }
            return tracks;
        } finally {
            if (mInputStream != null) {
                mInputStream.close();
            }
        }
    }

    /**
     * 验证MIDI文件头（必须以"MThd"开头）
     */
    private boolean verifyHeader() throws IOException {
        byte[] header = new byte[4];
        int read = mInputStream.read(header);
        if (read != 4) {
            LogUtils.d(TAG, "文件头读取不完整，读取字节数: " + read);
            return false;
        }
        String headerStr = new String(header, US_ASCII);
        boolean isValid = "MThd".equals(headerStr);
        if (!isValid) {
            LogUtils.d(TAG, "无效的文件头标识: " + headerStr 
					   + " (十六进制: " + bytesToHex(header) + ")");
        }
        return isValid;
    }

    /**
     * 读取MIDI文件头信息（提取每拍ticks数）
     */
    private void readHeaderInfo() throws IOException {
        // 1. 读取头长度（4字节，标准MIDI固定为6）
        int headerLength = readInt();
        LogUtils.d(TAG, "MIDI文件头长度: " + headerLength);

        // 2. 读取头数据（共6字节）
        byte[] headerData = new byte[6];
        int read = mInputStream.read(headerData);
        if (read != 6) {
            LogUtils.d(TAG, "文件头数据不完整，预期6字节，实际读取: " + read);
            throw new IOException("无效的MIDI文件头数据");
        }

        // 3. 解析头信息（格式类型、轨道数、每拍ticks数）
        int formatType = ((headerData[0] & 0xFF) << 8) | (headerData[1] & 0xFF);
        mTrackCount = ((headerData[2] & 0xFF) << 8) | (headerData[3] & 0xFF);
        mTicksPerBeat = ((headerData[4] & 0xFF) << 8) | (headerData[5] & 0xFF); // 存储每拍ticks数

        LogUtils.d(TAG, "MIDI文件格式: " + formatType);
        LogUtils.d(TAG, "时间分隔符(每拍 ticks): " + mTicksPerBeat);
        LogUtils.d(TAG, "解析到轨道数量: " + mTrackCount);

        // 4. 处理扩展头
        if (headerLength > 6) {
            long skipped = mInputStream.skip(headerLength - 6);
            LogUtils.d(TAG, "跳过扩展头字节数: " + skipped);
        }
    }

    /**
     * 解析单个轨道（包含事件的deltaTicks，用于速度控制）
     */
    private MidiPlayer.MidiTrack parseTrackWithTicks() throws IOException {
        // 1. 读取轨道头（MTrk）
        byte[] trackHeader = new byte[4];
        int headerRead = mInputStream.read(trackHeader);
        if (headerRead != 4) {
            LogUtils.d(TAG, "轨道头读取不完整，实际读取: " + headerRead + "字节");
            return new MidiPlayer.MidiTrack(new ArrayList<MidiPlayer.MidiEvent>());
        }

        // 2. 验证轨道头标识
        String headerStr = new String(trackHeader, US_ASCII);
        if (!"MTrk".equals(headerStr)) {
            LogUtils.d(TAG, "无效的轨道头标识: " + headerStr);
            return new MidiPlayer.MidiTrack(new ArrayList<MidiPlayer.MidiEvent>());
        }

        // 3. 读取轨道长度
        int trackLength = readInt();
        LogUtils.d(TAG, "解析轨道，长度: " + trackLength + "字节");

        // 4. 读取完整轨道数据
        byte[] trackData = new byte[trackLength];
        int totalRead = 0;
        while (totalRead < trackLength) {
            int bytesRead = mInputStream.read(trackData, totalRead, trackLength - totalRead);
            if (bytesRead == -1) {
                LogUtils.d(TAG, "轨道数据读取提前结束，已读取: " + totalRead);
                break;
            }
            totalRead += bytesRead;
        }

        // 5. 解析轨道事件（包含deltaTicks）
        List<MidiPlayer.MidiEvent> events = new ArrayList<MidiPlayer.MidiEvent>();
        if (totalRead == trackLength) {
            parseEventsWithTicks(events, trackData);
        } else {
            LogUtils.d(TAG, "轨道数据不完整，跳过事件解析");
        }

        return new MidiPlayer.MidiTrack(events);
    }

    /**
     * 解析轨道事件（提取deltaTicks，用于计算播放延迟）
     */
    private void parseEventsWithTicks(List<MidiPlayer.MidiEvent> events, byte[] trackData) {
        int offset = 0;
        while (offset < trackData.length) {
            // 1. 读取deltaTicks（事件间隔，单位ticks）
            long deltaTicks = readVariableLength(trackData, offset);
            int deltaSize = getVariableLengthSize(deltaTicks);
            offset += deltaSize;

            if (offset >= trackData.length) {
                break;
            }

            // 2. 读取事件状态字节
            int statusByte = trackData[offset] & 0xFF;
            offset++;

            // 3. 确定事件数据长度
            int dataLength = getEventDataLength(statusByte);
            if (offset + dataLength > trackData.length) {
                LogUtils.d(TAG, "事件数据不完整，状态字节: 0x" + Integer.toHexString(statusByte));
                break;
            }

            // 4. 提取事件数据（状态字节+数据字节）
            byte[] eventData = new byte[1 + dataLength];
            eventData[0] = (byte) statusByte;
            System.arraycopy(trackData, offset, eventData, 1, dataLength);
            offset += dataLength;

            // 5. 存储事件（包含deltaTicks）
            events.add(new MidiPlayer.MidiEvent(eventData, (int) deltaTicks));
        }

        LogUtils.d(TAG, "轨道事件解析完成，事件数量: " + events.size());
    }

    /**
     * 原始轨道解析方法（兼容旧逻辑）
     */
    private MidiTrack parseTrack() throws IOException {
        // 1. 读取轨道头（MTrk）
        byte[] trackHeader = new byte[4];
        int headerRead = mInputStream.read(trackHeader);
        if (headerRead != 4) {
            LogUtils.d(TAG, "轨道头读取不完整，实际读取: " + headerRead + "字节");
            return new MidiTrack();
        }

        // 2. 验证轨道头标识
        String headerStr = new String(trackHeader, US_ASCII);
        if (!"MTrk".equals(headerStr)) {
            LogUtils.d(TAG, "无效的轨道头标识: " + headerStr 
					   + " (十六进制: " + bytesToHex(trackHeader) + ")");
            return new MidiTrack();
        }

        // 3. 读取轨道长度
        int trackLength = readInt();
        LogUtils.d(TAG, "解析轨道，长度: " + trackLength + "字节");

        // 4. 读取完整轨道数据
        byte[] trackData = new byte[trackLength];
        int totalRead = 0;
        while (totalRead < trackLength) {
            int bytesRead = mInputStream.read(trackData, totalRead, trackLength - totalRead);
            if (bytesRead == -1) {
                LogUtils.d(TAG, "轨道数据读取提前结束，已读取: " + totalRead + "，预期: " + trackLength);
                break;
            }
            totalRead += bytesRead;
        }

        // 5. 解析轨道事件
        MidiTrack track = new MidiTrack();
        if (totalRead == trackLength) {
            parseEvents(track, trackData);
        } else {
            LogUtils.d(TAG, "轨道数据不完整，跳过事件解析");
        }

        return track;
    }

    /**
     * 原始事件解析方法（兼容旧逻辑）
     */
    private void parseEvents(MidiTrack track, byte[] trackData) {
        int offset = 0;
        while (offset < trackData.length) {
            // 1. 读取可变长度时间戳（MIDI事件时间差）
            long deltaTime = readVariableLength(trackData, offset);
            offset += getVariableLengthSize(deltaTime);

            if (offset >= trackData.length) {
                break;
            }

            // 2. 读取事件状态字节
            int statusByte = trackData[offset] & 0xFF;
            offset++;

            // 3. 确定事件数据长度
            int dataLength = getEventDataLength(statusByte);
            if (offset + dataLength > trackData.length) {
                LogUtils.d(TAG, "事件数据不完整，状态字节: 0x" + Integer.toHexString(statusByte)
						   + "，剩余字节: " + (trackData.length - offset));
                break;
            }

            // 4. 提取完整事件（状态字节+数据字节）
            byte[] event = new byte[1 + dataLength];
            event[0] = (byte) statusByte;
            System.arraycopy(trackData, offset, event, 1, dataLength);
            offset += dataLength;

            track.addEvent(event);
        }

        LogUtils.d(TAG, "轨道事件解析完成，事件数量: " + track.getEventCount());
    }

    /**
     * 根据状态字节获取事件数据长度
     */
    private int getEventDataLength(int statusByte) {
        int eventType = statusByte >> 4;
        switch (eventType) {
            case 0x8: // 音符关闭
            case 0x9: // 音符开启
            case 0xA: // 触后
            case 0xB: // 控制器
            case 0xE: // 弯音
                return 2;
            case 0xC: // 程序变更
            case 0xD: // 通道触后
                return 1;
            case 0xF: // 系统事件
                if (statusByte == 0xFF) { // 元事件
                    return 1;
                } else if (statusByte == 0xF0 || statusByte == 0xF7) { // 系统专属事件
                    return 0;
                }
            default:
                return 0;
        }
    }

    /**
     * 读取可变长度整数（MIDI事件时间差deltaTicks）
     */
    private long readVariableLength(byte[] data, int offset) {
        long value = 0;
        int b;
        int i = 0;
        do {
            b = data[offset + i] & 0xFF;
            value = (value << 7) | (b & 0x7F);
            i++;
        } while ((b & 0x80) != 0 && i < 4); // 最多4字节
        return value;
    }

    /**
     * 获取可变长度整数的字节数
     */
    private int getVariableLengthSize(long value) {
        if (value < 0x80) return 1;
        if (value < 0x4000) return 2;
        if (value < 0x200000) return 3;
        return 4;
    }

    /**
     * 读取无符号短整型（2字节，大端序）
     */
    private int readUnsignedShort() throws IOException {
        int b1 = mInputStream.read() & 0xFF;
        int b2 = mInputStream.read() & 0xFF;
        return (b1 << 8) | b2;
    }

    /**
     * 读取整型（4字节，大端序）
     */
    private int readInt() throws IOException {
        int b1 = mInputStream.read() & 0xFF;
        int b2 = mInputStream.read() & 0xFF;
        int b3 = mInputStream.read() & 0xFF;
        int b4 = mInputStream.read() & 0xFF;
        return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
    }

    /**
     * 字节数组转十六进制字符串（调试用）
     */
    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X ", b));
        }
        return sb.toString().trim();
    }

    /**
     * 旧轨道类（兼容旧逻辑）
     */
    /*public static class MidiTrack {
        private List<byte[]> events = new ArrayList<byte[]>();
        private boolean isMuted = false;
        private int currentIndex = 0;

        public void addEvent(byte[] event) {
            events.add(event);
        }

        public int getEventCount() {
            return events.size();
        }

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

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

        public byte[] nextEvent() {
            if (currentIndex < events.size()) {
                return events.get(currentIndex++);
            }
            return null;
        }

        public void reset() {
            currentIndex = 0;
        }
    }*/
}

