/*  Copyright (C) 2023-2024 José Rebelo, Yoran Vulker

    This file is part of Gadgetbridge.

    Gadgetbridge is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Gadgetbridge is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl;

import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ACTIVE_SECONDS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ALTITUDE_AVG;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ALTITUDE_MAX;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ALTITUDE_MIN;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.CADENCE_AVG;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.CALORIES_BURNT;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.DISTANCE_METERS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ELEVATION_GAIN;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.ELEVATION_LOSS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_AVG;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_MAX;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_MIN;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_AEROBIC;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_ANAEROBIC;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_EXTREME;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_FAT_BURN;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.HR_ZONE_WARM_UP;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.LAPS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.LAP_PACE_AVERAGE;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.MAXIMUM_OXYGEN_UPTAKE;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.PACE_AVG_SECONDS_KM;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.PACE_MAX;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.PACE_MIN;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.RECOVERY_TIME;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.SPEED_AVG;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.SPEED_MAX;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.STEPS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.STROKES;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.STROKE_RATE_AVG;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.SWIM_STYLE;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.SWOLF_AVG;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TIME_END;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TIME_START;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TRAINING_EFFECT_AEROBIC;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.TRAINING_EFFECT_ANAEROBIC;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_BPM;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_HOURS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_KCAL;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_KMPH;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_LAPS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_METERS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_ML_KG_MIN;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_NONE;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS_PER_KM;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SECONDS_PER_M;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_SPM;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_STEPS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_STROKES;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_STROKES_PER_MINUTE;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.UNIT_UNIX_EPOCH_SECONDS;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries.WORKOUT_LOAD;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.impl.XiaomiSimpleActivityParser.XIAOMI_WORKOUT_TYPE;

import android.widget.Toast;

import androidx.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.XiaomiSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityFileId;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xiaomi.activity.XiaomiActivityParser;
import nodomain.freeyourgadget.gadgetbridge.util.CheckSums;
import nodomain.freeyourgadget.gadgetbridge.util.GB;

public class WorkoutSummaryParser extends XiaomiActivityParser implements ActivitySummaryParser {
    private static final Logger LOG = LoggerFactory.getLogger(WorkoutSummaryParser.class);

    @Override
    public boolean parse(final XiaomiSupport support, final XiaomiActivityFileId fileId, final byte[] bytes) {
        BaseActivitySummary summary = new BaseActivitySummary();

        summary.setStartTime(fileId.getTimestamp()); // due to a bug this has to be set
        summary.setEndTime(fileId.getTimestamp()); // due to a bug this has to be set
        summary.setActivityKind(ActivityKind.UNKNOWN.getCode());
        summary.setRawSummaryData(bytes);

        try {
            summary = parseBinaryData(summary, true);
        } catch (final Exception e) {
            LOG.error("Failed to parse workout summary", e);
            GB.toast(support.getContext(), "Failed to parse workout summary", Toast.LENGTH_LONG, GB.ERROR, e);
            return false;
        }

        // parseBinaryData may return null in case the version is not supported
        if (summary == null) {
            LOG.warn("summary is null - should never happen {}", fileId);
            return false;
        }

        summary.setSummaryData(null); // remove json before saving to database

        try (DBHandler dbHandler = GBApplication.acquireDB()) {
            final DaoSession session = dbHandler.getDaoSession();
            final Device device = DBHelper.getDevice(support.getDevice(), session);
            final User user = DBHelper.getUser(session);

            final BaseActivitySummary existingSummary = findOrCreateBaseActivitySummary(session, device, user, fileId);
            existingSummary.setEndTime(summary.getEndTime());
            existingSummary.setActivityKind(summary.getActivityKind());
            existingSummary.setRawSummaryData(summary.getRawSummaryData());
            existingSummary.setSummaryData(null);  // remove json before saving to database

            session.getBaseActivitySummaryDao().insertOrReplace(existingSummary);
        } catch (final Exception e) {
            GB.toast(support.getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, e);
            return false;
        }

        return true;
    }

    @Override
    public BaseActivitySummary parseBinaryData(final BaseActivitySummary summary, final boolean forDetails) {
        final byte[] data = summary.getRawSummaryData();
        if (data == null) {
            return summary;
        }

        final int arrCrc32 = CheckSums.getCRC32(data, 0, data.length - 4);
        final int expectedCrc32 = BLETypeConversions.toUint32(data, data.length - 4);

        final ByteBuffer buf;
        if (arrCrc32 != expectedCrc32) {
            // If the CRC32 is not valid, we're missing 1 header padding byte due to a previous bug
            // This previous version also did not include the CRC at the end
            // More info: https://codeberg.org/Freeyourgadget/Gadgetbridge/issues/3916
            buf = ByteBuffer.allocate(data.length + 1).order(ByteOrder.LITTLE_ENDIAN);
            buf.put(data, 0, 7);
            buf.put((byte) 0);
            buf.put(data, 7, data.length - 7);
            buf.flip();
        } else {
            // Valid full file, skip crc
            buf = ByteBuffer.wrap(data, 0, data.length - 4).order(ByteOrder.LITTLE_ENDIAN);
        }

        final XiaomiActivityFileId fileId = XiaomiActivityFileId.from(buf);

        final byte fileIdPadding = buf.get();
        if (fileIdPadding != 0) {
            LOG.warn("Expected 0 padding after fileId, got {} - parsing might fail", fileIdPadding);
        }

        XiaomiSimpleActivityParser parser = null;

        switch (fileId.getSubtype()) {
            case SPORTS_OUTDOOR_WALKING_V1:
                summary.setActivityKind(ActivityKind.WALKING.getCode());
                parser = getOutdoorWalkingV1Parser(fileId);
                break;
            case SPORTS_OUTDOOR_RUNNING:
                summary.setActivityKind(ActivityKind.RUNNING.getCode());
                parser = getOutdoorWalkingV1Parser(fileId);
                break;
            case SPORTS_INDOOR_CYCLING:
                summary.setActivityKind(ActivityKind.INDOOR_CYCLING.getCode());
                parser = getIndoorCyclingParser(fileId);
                break;
            case SPORTS_FREESTYLE:
                summary.setActivityKind(ActivityKind.STRENGTH_TRAINING.getCode());
                parser = getFreestyleParser(fileId);
                break;
            case SPORTS_POOL_SWIMMING:
                summary.setActivityKind(ActivityKind.SWIMMING.getCode());
                parser = getPoolSwimmingParser(fileId);
                break;
            case SPORTS_HIIT:
                summary.setActivityKind(ActivityKind.EXERCISE.getCode());
                parser = getHiitParser(fileId);
                break;
            case SPORTS_ELLIPTICAL:
                summary.setActivityKind(ActivityKind.ELLIPTICAL_TRAINER.getCode());
                // TODO
                break;
            case SPORTS_OUTDOOR_WALKING_V2:
                parser = getOutdoorWalkingV2Parser(fileId);
                break;
            case SPORTS_OUTDOOR_CYCLING:
                parser = getOutdoorCyclingParser(fileId);
                break;
            default:
                LOG.warn("No workout summary parser for {}", fileId.getSubtypeCode());
                break;
        }

        if (parser != null) {
            parser.parse(summary, buf);
        }

        return summary;
    }

    @Nullable
    private XiaomiSimpleActivityParser getFreestyleParser(final XiaomiActivityFileId fileId) {
        final int version = fileId.getVersion();
        final int headerSize;
        switch (version) {
            case 8:
                headerSize = 6;
                break;
            default:
                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
                return null;
        }

        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
        builder.setHeaderSize(headerSize);
        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
        builder.addByte(HR_AVG, UNIT_BPM);
        builder.addByte(HR_MAX, UNIT_BPM);
        builder.addByte(HR_MIN, UNIT_BPM);
        builder.addUnknown(6);
        builder.addFloat(TRAINING_EFFECT_AEROBIC, UNIT_NONE);
        builder.addUnknown(1);
        builder.addUnknown(1);
        builder.addShort(RECOVERY_TIME, UNIT_HOURS);
        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
        builder.addUnknown(2);
        builder.addUnknown(4);
        builder.addFloat(TRAINING_EFFECT_ANAEROBIC, UNIT_NONE);
        builder.addUnknown(3);

        return builder.build();
    }

    @Nullable
    private XiaomiSimpleActivityParser getIndoorCyclingParser(final XiaomiActivityFileId fileId) {
        final int version = fileId.getVersion();
        final int headerSize;
        switch (version) {
            case 8:
                headerSize = 7;
                break;
            default:
                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
                return null;
        }

        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
        builder.setHeaderSize(headerSize);
        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
        builder.addUnknown(4);
        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
        builder.addUnknown(4);
        builder.addByte(HR_AVG, UNIT_BPM);
        builder.addByte(HR_MAX, UNIT_BPM);
        builder.addByte(HR_MIN, UNIT_BPM);
        builder.addFloat(TRAINING_EFFECT_AEROBIC, UNIT_NONE);
        builder.addUnknown(1);
        builder.addUnknown(1);
        builder.addShort(RECOVERY_TIME, UNIT_HOURS);
        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
        builder.addUnknown(2);
        builder.addUnknown(4);
        builder.addFloat(TRAINING_EFFECT_ANAEROBIC, UNIT_NONE);
        // FIXME identify field lengths to align with the header
        builder.addUnknown(3);
        builder.addInt("configuredTimeGoal", UNIT_SECONDS);
        builder.addShort("configuredCaloriesGoal", UNIT_KCAL);
        builder.addShort("maximumCaloriesGoal", UNIT_KCAL);  // TODO: mhm?
        builder.addUnknown(28);
        builder.addShort(WORKOUT_LOAD, UNIT_NONE); // training load
        builder.addUnknown(24);
        builder.addByte("configuredSets", UNIT_NONE);
        builder.addUnknown(13);
        builder.addInt("startTime2", UNIT_SECONDS);
        builder.addInt("endTime2", UNIT_SECONDS);
        builder.addInt("goal", UNIT_NONE); // TODO match against goalType
        builder.addInt("duration2", UNIT_SECONDS);
        builder.addInt("intervalTime", UNIT_SECONDS);
        builder.addUnknown(56);
        builder.addInt("hrZoneExtreme2", UNIT_SECONDS);
        builder.addInt("hrZoneAnaerobic2", UNIT_SECONDS);
        builder.addInt("hrZoneAerobic2", UNIT_SECONDS);
        builder.addInt("hrZoneFatBurn2", UNIT_SECONDS);
        builder.addInt("hrZoneWarmUp2", UNIT_SECONDS);
        builder.addUnknown(16);
        builder.addShort("vitality_gain", UNIT_NONE);
        builder.addShort("training_load2", UNIT_NONE);
        builder.addShort("recovery_time2", UNIT_HOURS);

        return builder.build();
    }

    @Nullable
    private XiaomiSimpleActivityParser getOutdoorWalkingV1Parser(final XiaomiActivityFileId fileId) {
        final int version = fileId.getVersion();
        final int headerSize;
        switch (version) {
            case 4:
                headerSize = 4;
                break;
            default:
                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
                return null;
        }

        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
        builder.setHeaderSize(headerSize);
        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
        builder.addInt(DISTANCE_METERS, UNIT_METERS);
        builder.addInt(CALORIES_BURNT, UNIT_KCAL);
        builder.addInt(PACE_MAX, UNIT_SECONDS_PER_M);
        builder.addInt(PACE_MIN, UNIT_SECONDS_PER_M);
        builder.addUnknown(4);
        builder.addInt(STEPS, UNIT_STEPS);
        builder.addUnknown(2); // pace?
        builder.addByte(HR_AVG, UNIT_BPM);
        builder.addByte(HR_MAX, UNIT_BPM);
        builder.addByte(HR_MIN, UNIT_BPM);
        // FIXME identify field lengths to align with the header
        builder.addUnknown(20);
        builder.addFloat("recoveryValue", "recoveryValue");
        builder.addUnknown(9);
        builder.addByte(RECOVERY_TIME, UNIT_SECONDS);
        builder.addUnknown(2);
        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
        builder.addInt("configured_time_goal", UNIT_SECONDS);

        return builder.build();
    }

    @Nullable
    private XiaomiSimpleActivityParser getOutdoorWalkingV2Parser(final XiaomiActivityFileId fileId) {
        final int version = fileId.getVersion();
        final int headerSize;
        switch (version) {
            case 4:
                headerSize = 7;
                break;
            case 5:
                headerSize = 9;
                break;
            default:
                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
                return null;
        }

        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
        builder.setHeaderSize(headerSize);
        builder.addShort(XIAOMI_WORKOUT_TYPE, XIAOMI_WORKOUT_TYPE);
        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
        builder.addUnknown(4);
        builder.addInt(DISTANCE_METERS, UNIT_METERS);
        builder.addUnknown(2);
        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
        if (version >= 5) {
            builder.addInt(PACE_AVG_SECONDS_KM, UNIT_SECONDS_PER_KM);
        }
        builder.addInt(PACE_MAX, UNIT_SECONDS_PER_KM);
        builder.addInt(PACE_MIN, UNIT_SECONDS_PER_KM);
        if (version >= 5) {
            builder.addFloat(SPEED_AVG, UNIT_KMPH);
        }
        builder.addFloat(SPEED_MAX, UNIT_KMPH);
        builder.addInt(STEPS, UNIT_STEPS);
        if (version >= 5) {
            builder.addUnknown(4);
        }
        builder.addUnknown(2);
        builder.addByte(HR_AVG, UNIT_BPM);
        builder.addByte(HR_MAX, UNIT_BPM);
        builder.addByte(HR_MIN, UNIT_BPM);
        builder.addUnknown(20);
        builder.addFloat("recoveryValue", "?");
        builder.addUnknown(9);
        builder.addByte(RECOVERY_TIME, UNIT_HOURS);
        builder.addUnknown(2);
        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);
        builder.addInt("configuredTimeGoal", UNIT_SECONDS);
        builder.addShort("configuredCaloriesGoal", UNIT_KCAL);
        builder.addInt("configuredDistanceGoal", UNIT_METERS);
        builder.addUnknown(11);
        builder.addShort(WORKOUT_LOAD, UNIT_NONE); // training load
        builder.addUnknown(24);
        builder.addByte("averageHR2", UNIT_BPM);
        builder.addByte("maxHR2", UNIT_BPM);
        builder.addByte("minHR2", UNIT_BPM);
        builder.addUnknown(2);
        builder.addByte(CADENCE_AVG, UNIT_SPM);

        return builder.build();
    }

    @Nullable
    private XiaomiSimpleActivityParser getOutdoorCyclingParser(final XiaomiActivityFileId fileId) {
        final int version = fileId.getVersion();
        final int headerSize;
        switch (version) {
            case 4:
            case 5:
                headerSize = 6;
                break;
            default:
                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
                return null;
        }

        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
        builder.setHeaderSize(headerSize);
        builder.addShort(XIAOMI_WORKOUT_TYPE, XIAOMI_WORKOUT_TYPE);
        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
        builder.addUnknown(4);
        builder.addInt(DISTANCE_METERS, UNIT_METERS);
        builder.addUnknown(2);
        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
        builder.addUnknown(4);
        builder.addUnknown(4);
        if (version >= 5) {
            builder.addFloat(SPEED_AVG, UNIT_KMPH);
        }
        builder.addFloat(SPEED_MAX, UNIT_KMPH);
        builder.addByte(HR_AVG, UNIT_BPM);
        builder.addByte(HR_MAX, UNIT_BPM);
        builder.addByte(HR_MIN, UNIT_BPM);
        builder.addFloat(ELEVATION_GAIN, UNIT_METERS);
        builder.addFloat(ELEVATION_LOSS, UNIT_METERS);
        builder.addFloat(ALTITUDE_AVG, UNIT_METERS);
        builder.addFloat(ALTITUDE_MAX, UNIT_METERS);
        builder.addFloat(ALTITUDE_MIN, UNIT_METERS);
        builder.addFloat(TRAINING_EFFECT_AEROBIC, UNIT_NONE);
        builder.addUnknown(1);
        builder.addFloat(TRAINING_EFFECT_ANAEROBIC, UNIT_NONE);
        builder.addUnknown(1);
        builder.addByte(MAXIMUM_OXYGEN_UPTAKE, UNIT_ML_KG_MIN);
        builder.addUnknown(1);
        builder.addUnknown(1);
        builder.addShort(RECOVERY_TIME, UNIT_HOURS);
        builder.addUnknown(1);
        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);

        return builder.build();
    }

    @Nullable
    private XiaomiSimpleActivityParser getHiitParser(final XiaomiActivityFileId fileId){
        final int version = fileId.getVersion();
        final int headerSize;
        switch (version) {
            case 5:
                headerSize = 4;
                break;
            default:
                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
                return null;
        }

        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
        builder.setHeaderSize(headerSize);
        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
        builder.addByte(HR_AVG, UNIT_BPM);
        builder.addByte(HR_MAX, UNIT_BPM);
        builder.addByte(HR_MIN, UNIT_BPM);
        builder.addFloat(TRAINING_EFFECT_AEROBIC, UNIT_NONE);
        builder.addUnknown(1);
        builder.addUnknown(1);
        builder.addShort(RECOVERY_TIME, UNIT_HOURS);
        builder.addInt(HR_ZONE_EXTREME, UNIT_SECONDS);
        builder.addInt(HR_ZONE_ANAEROBIC, UNIT_SECONDS);
        builder.addInt(HR_ZONE_AEROBIC, UNIT_SECONDS);
        builder.addInt(HR_ZONE_FAT_BURN, UNIT_SECONDS);
        builder.addInt(HR_ZONE_WARM_UP, UNIT_SECONDS);

        return builder.build();
    }

    @Nullable
    private XiaomiSimpleActivityParser getPoolSwimmingParser(final XiaomiActivityFileId fileId) {
        final int version = fileId.getVersion();
        final int headerSize;
        switch (version) {
            case 6:
                headerSize = 4;
                break;
            default:
                LOG.warn("Unable to parse workout summary version {}", fileId.getVersion());
                return null;
        }

        final XiaomiSimpleActivityParser.Builder builder = new XiaomiSimpleActivityParser.Builder();
        builder.setHeaderSize(headerSize);
        builder.addInt(TIME_START, UNIT_UNIX_EPOCH_SECONDS);
        builder.addInt(TIME_END, UNIT_UNIX_EPOCH_SECONDS);
        builder.addInt(ACTIVE_SECONDS, UNIT_SECONDS);
        builder.addInt(DISTANCE_METERS, UNIT_METERS);
        builder.addShort(CALORIES_BURNT, UNIT_KCAL);
        builder.addUnknown(11);
        builder.addShort(STROKES, UNIT_STROKES);
        builder.addByte(SWIM_STYLE, UNIT_NONE);
        builder.addUnknown(1);
        builder.addShort(LAPS, UNIT_LAPS);
        builder.addShort(SWOLF_AVG, UNIT_NONE);
        builder.addUnknown(2); // minSWOLF
        builder.addByte("configuredLaneLength", UNIT_METERS);
        builder.addUnknown(6);
        // builder.addInt("activeSec2", UNIT_SECONDS);
        builder.addInt("configuredTimeGoal", UNIT_SECONDS);
        builder.addShort("configuredCaloriesGoal", UNIT_KCAL);
        builder.addUnknown(8);
        builder.addByte("configuredLengthsGoal", UNIT_NONE);
        builder.addUnknown(14);
        builder.addByte("vitality_gain", UNIT_NONE);
        builder.addUnknown(60);
        // builder.addInt("startingTime2", UNIT_UNIX_EPOCH_SECONDS);
        // builder.addInt("endTime2", UNIT_UNIX_EPOCH_SECONDS);
        // builder.addUnknown(4);
        // builder.addInt("activeSec3", UNIT_SECONDS);
        // builder.addInt("activeSec4", UNIT_SECONDS);
        // builder.addUnknown(8);
        // builder.addInt("caloriesBurnt2", UNIT_KCAL);
        // builder.addInt("distanceMeter2", UNIT_METERS);
        // builder.addUnknown(4);
        builder.addInt(LAP_PACE_AVERAGE, UNIT_SECONDS);
        builder.addInt(PACE_MAX, UNIT_SECONDS); // not confirmed
        builder.addUnknown(4);
        builder.addInt(STROKE_RATE_AVG, UNIT_STROKES_PER_MINUTE);
        builder.addUnknown(14);
        // builder.addInt("laps2", UNIT_LAPS);
        // builder.addShort("avgSWOLF2", UNIT_NONE);
        builder.addShort("minSWOLF", UNIT_NONE);
        builder.addShort("maxSWOLF", UNIT_NONE);
        // builder.addInt("totalStrokes2", UNIT_STROKES);

        return builder.build();
    }
}
