/*  Copyright (C) 2024 Damien Gaignon, Martin.JM

    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.huawei;

import android.content.Context;
import android.widget.Toast;

import androidx.core.content.ContextCompat;

import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;

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

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

import de.greenrobot.dao.query.CloseableListIterator;
import de.greenrobot.dao.query.QueryBuilder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.TimestampTranslation;
import nodomain.freeyourgadget.gadgetbridge.activities.workouts.charts.DefaultWorkoutCharts;
import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryProgressEntry;
import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryTableRowEntry;
import nodomain.freeyourgadget.gadgetbridge.activities.workouts.entries.ActivitySummaryValue;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.HuaweiHeartRateZonesSpec;
import nodomain.freeyourgadget.gadgetbridge.devices.huawei.packets.Workout;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutDataSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutPaceSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummaryAdditionalValuesSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummaryAdditionalValuesSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSummarySampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSwimSegmentsSample;
import nodomain.freeyourgadget.gadgetbridge.entities.HuaweiWorkoutSwimSegmentsSampleDao;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryData;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryEntries;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
import nodomain.freeyourgadget.gadgetbridge.model.heartratezones.HeartRateZones;
import nodomain.freeyourgadget.gadgetbridge.model.heartratezones.HeartRateZonesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.workout.WorkoutChart;
import nodomain.freeyourgadget.gadgetbridge.model.DistanceUnit;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;

/**
 * This class parses the Huawei workouts into the table GB uses to show the workouts
 * It also re-parses the unknown data from the workout tables
 * It is a separate class so it can easily be used to re-parse the data without database migrations
 */
public class HuaweiWorkoutGbParser implements ActivitySummaryParser {
    private static final Logger LOG = LoggerFactory.getLogger(HuaweiWorkoutGbParser.class);

    public static class HuaweiActivityPoint extends ActivityPoint {
        private int swolf = -1;
        private int strokeRate = -1;
        private int frequency = -1;

        public int getSwolf() {
            return swolf;
        }

        public void setSwolf(int swolf) {
            this.swolf = swolf;
        }

        public int getStrokeRate() {
            return strokeRate;
        }

        public void setStrokeRate(int strokeRate) {
            this.strokeRate = strokeRate;
        }

        public int getFrequency() {
            return frequency;
        }

        public void setFrequency(int frequency) {
            this.frequency = frequency;
        }
    }

    // TODO: Might be nicer to propagate the exceptions, so they can be handled upstream

    private final GBDevice gbDevice;
    private final Context context;

    public HuaweiWorkoutGbParser(final GBDevice gbDevice, final Context context) {
        this.gbDevice = gbDevice;
        this.context = context;
    }

    @Override
    public BaseActivitySummary parseBinaryData(final BaseActivitySummary summary, final boolean forDetails) {
        // FIXME Do not use this
        return parseWorkout(summary, forDetails).getSummary();
    }

    private static WorkoutChart createSwolfChart(final Context context,
                                                 final List<Entry> swolfDataPoints) {
        final String label = String.format("%s", context.getString(R.string.swolfIndex));
        final LineDataSet dataset = DefaultWorkoutCharts.createLineDataSet(context, swolfDataPoints, label, ContextCompat.getColor(context, R.color.chart_line_swolf));
        return new WorkoutChart(
                "swolf",
                context.getString(R.string.swolfIndex),
                ActivitySummaryEntries.GROUP_SWIMMING,
                new LineData(dataset),
                null,
                DefaultWorkoutCharts.getUnitString(context, ActivitySummaryEntries.UNIT_NONE)
        );
    }

    private static WorkoutChart createStrokeRateChart(final Context context,
                                                      final List<Entry> strokesDataPoints) {
        final String label = String.format("%s (%s)", context.getString(R.string.stroke_rate), DefaultWorkoutCharts.getUnitString(context, ActivitySummaryEntries.UNIT_STROKES_PER_MINUTE));
        final LineDataSet dataset = DefaultWorkoutCharts.createLineDataSet(context, strokesDataPoints, label, ContextCompat.getColor(context, R.color.chart_line_stroke_rate));
        return new WorkoutChart(
                "strokesRate",
                context.getString(R.string.stroke_rate),
                ActivitySummaryEntries.GROUP_STROKES,
                new LineData(dataset),
                null,
                DefaultWorkoutCharts.getUnitString(context, ActivitySummaryEntries.UNIT_STROKES_PER_MINUTE)
        );
    }

    private static WorkoutChart createFrequencyChart(final Context context,
                                                     final List<Entry> frequencyDataPoints) {
        final String label = String.format("%s (%s)", context.getString(R.string.Speed), DefaultWorkoutCharts.getUnitString(context, ActivitySummaryEntries.UNIT_JUMPS_PER_MINUTE));
        final LineDataSet dataset = DefaultWorkoutCharts.createLineDataSet(context, frequencyDataPoints, label, ContextCompat.getColor(context, R.color.chart_line_speed));
        return new WorkoutChart(
                "frequency",
                context.getString(R.string.Speed),
                ActivitySummaryEntries.GROUP_JUMPS,
                new LineData(dataset),
                null,
                DefaultWorkoutCharts.getUnitString(context, ActivitySummaryEntries.UNIT_JUMPS_PER_MINUTE)
        );
    }

    private List<WorkoutChart> buildHuaweiCharts(final Context context,
                                                 final List<HuaweiActivityPoint> activityPoints) {

        final List<WorkoutChart> charts = new LinkedList<>();
        final TimestampTranslation tsTranslation = new TimestampTranslation();
        final List<Entry> swolfDataPoints = new ArrayList<>();
        final List<Entry> strokeRateDataPoints = new ArrayList<>();
        final List<Entry> frequencyDataPoints = new ArrayList<>();

        for (int i = 0; i <= activityPoints.size() - 1; i++) {
            final HuaweiActivityPoint point = activityPoints.get(i);
            final long tsShorten = tsTranslation.shorten((int) point.getTime().getTime());
            if (point.getSwolf() >= 0) {
                swolfDataPoints.add(new Entry(tsShorten, point.getSwolf()));
            }
            if (point.getStrokeRate() >= 0) {
                strokeRateDataPoints.add(new Entry(tsShorten, point.getStrokeRate()));
            }
            if (point.getFrequency() >= 0) {
                frequencyDataPoints.add(new Entry(tsShorten, point.getFrequency()));
            }
        }
        if (!swolfDataPoints.isEmpty()) {
            charts.add(createSwolfChart(context, swolfDataPoints));
        }
        if (!strokeRateDataPoints.isEmpty()) {
            charts.add(createStrokeRateChart(context, strokeRateDataPoints));
        }
        if (!frequencyDataPoints.isEmpty()) {
            charts.add(createFrequencyChart(context, frequencyDataPoints));
        }
        return charts;
    }

    @Override
    public nodomain.freeyourgadget.gadgetbridge.model.workout.Workout parseWorkout(BaseActivitySummary summary, final boolean forDetails) {
        if (!forDetails) {
            final ActivitySummaryData activitySummaryData = ActivitySummaryData.fromJson(summary.getSummaryData());
            if (summary.getGpxTrack() == null) {
                // Quickly check and update whether the activity has gps
                try (DBHandler db = GBApplication.acquireDB()) {
                    final DaoSession session = db.getDaoSession();
                    final Device device = DBHelper.getDevice(gbDevice, session);
                    final User user = DBHelper.getUser(session);
                    final QueryBuilder<HuaweiWorkoutSummarySample> qb = session.getHuaweiWorkoutSummarySampleDao().queryBuilder();
                    qb.where(HuaweiWorkoutSummarySampleDao.Properties.StartTimestamp.eq(summary.getStartTime().getTime() / 1000));
                    qb.where(HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(device.getId()));
                    qb.where(HuaweiWorkoutSummarySampleDao.Properties.UserId.eq(user.getId()));
                    final List<HuaweiWorkoutSummarySample> huaweiSummaries = qb.build().list();
                    if (!huaweiSummaries.isEmpty()) {
                        activitySummaryData.setHasGps(huaweiSummaries.get(0).getRawGpsFileLocation() != null);
                    }
                } catch (Exception e) {
                    LOG.error("Failed to check whether activity has gps");
                }
            }

            // Our parsing is too slow, especially without a RecyclerView
            return new nodomain.freeyourgadget.gadgetbridge.model.workout.Workout(summary, activitySummaryData);
        }

        // Find the existing HuaweiWorkoutSummarySample
        try (DBHandler db = GBApplication.acquireDB()) {
            final DaoSession session = db.getDaoSession();
            final Device device = DBHelper.getDevice(gbDevice, session);
            final User user = DBHelper.getUser(session);

            QueryBuilder<HuaweiWorkoutSummarySample> qb = session.getHuaweiWorkoutSummarySampleDao().queryBuilder();
            qb.where(HuaweiWorkoutSummarySampleDao.Properties.StartTimestamp.eq(summary.getStartTime().getTime() / 1000));
            qb.where(HuaweiWorkoutSummarySampleDao.Properties.DeviceId.eq(device.getId()));
            qb.where(HuaweiWorkoutSummarySampleDao.Properties.UserId.eq(user.getId()));
            final List<HuaweiWorkoutSummarySample> huaweiSummaries = qb.build().list();
            if (huaweiSummaries.isEmpty()) {
                LOG.warn("Failed to find huawei summary for {}", summary.getStartTime());
                return new nodomain.freeyourgadget.gadgetbridge.model.workout.Workout(summary, ActivitySummaryData.fromJson(summary.getSummaryData()));
            }
            final List<HuaweiActivityPoint> activityPoints = new ArrayList<>();
            final ActivitySummaryData activitySummaryData = updateBaseSummary(session, huaweiSummaries.get(0), summary, activityPoints);
            final ActivityKind activityKind = ActivityKind.fromCode(summary.getActivityKind());
            final List<WorkoutChart> charts = new LinkedList<>();
            if (!activityPoints.isEmpty()) {
                charts.addAll(DefaultWorkoutCharts.buildDefaultCharts(context, activityPoints, activityKind));
                charts.addAll(buildHuaweiCharts(context, activityPoints));
            }
            byte[] recoveryHR = huaweiSummaries.get(0).getRecoveryHeartRates();
            if (recoveryHR != null && recoveryHR.length > 0) {
                // starts from workoutEndTime - 5000
                byte[] recHR = StringUtils.hexToBytes(new String(recoveryHR));
                final List<Entry> heartRateDataPoints = new ArrayList<>();
                for (int i = 0; i < recHR.length; i++) {
                    heartRateDataPoints.add(new Entry(i * 5000, recHR[i] & 0xFF));
                }
                charts.add(createRecoveryHeartRateChart(context, heartRateDataPoints));
            }
            return new nodomain.freeyourgadget.gadgetbridge.model.workout.Workout(
                    summary,
                    activitySummaryData,
                    charts
            );
        } catch (Exception e) {
            LOG.error("Failed to update summary");
        }

        return new nodomain.freeyourgadget.gadgetbridge.model.workout.Workout(summary, ActivitySummaryData.fromJson(summary.getSummaryData()));
    }

    public enum HuaweiActivityType {
        // Based on nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.enums.GarminSport

        RUNNING(1, ActivityKind.RUNNING),
        WALKING(2, ActivityKind.WALKING),
        CYCLING(3, ActivityKind.CYCLING),
        MOUNTAIN_HIKE(4, ActivityKind.MOUNTAIN_HIKE),
        INDOOR_RUN(5, ActivityKind.INDOOR_RUNNING),
        POOL_SWIM(6, ActivityKind.POOL_SWIM),
        INDOOR_CYCLE(7, ActivityKind.INDOOR_CYCLING),
        OPEN_WATER_SWIM(8, ActivityKind.SWIMMING_OPENWATER),
        TRAIL_RUN(11, ActivityKind.TRAIL_RUN),
        INDOOR_WALK(13, ActivityKind.INDOOR_WALKING),
        HIKING(14, ActivityKind.HIKING),
        JUMP_ROPING(21, ActivityKind.JUMP_ROPING),
        FREE_DIVING(22, ActivityKind.FREE_DIVING),
        APNEA_TRAINING(23, ActivityKind.APNEA_TRAINING),
        APNEA_TEST(24, ActivityKind.APNEA_TEST),
        SCUBA_DIVING(25, ActivityKind.SCUBA_DIVING),
        PING_PONG(128, ActivityKind.PINGPONG),
        BADMINTON(129, ActivityKind.BADMINTON),
        TENNIS(130, ActivityKind.TENNIS),
        SOCCER(131, ActivityKind.SOCCER),
        BASKETBALL(132, ActivityKind.BASKETBALL),
        VOLLEYBALL(133, ActivityKind.VOLLEYBALL),
        ELLIPTICAL_TRAINER(134, ActivityKind.ELLIPTICAL_TRAINER),
        ROWING_MACHINE(135, ActivityKind.ROWING_MACHINE),
        STEPPER(136, ActivityKind.STEPPER),
        YOGA(137, ActivityKind.YOGA),
        PILATES(138, ActivityKind.PILATES),
        AEROBICS(139, ActivityKind.AEROBICS),
        STRENGTH_TRAINING(140, ActivityKind.STRENGTH_TRAINING),
        SPINNING(141, ActivityKind.SPINNING),
        AIR_WALKER(142, ActivityKind.AIR_WALKER),
        HIIT(143, ActivityKind.HIIT),
        CROSSFIT(145, ActivityKind.CROSSFIT),
        FUNCTIONAL_TRAINING(146, ActivityKind.FUNCTIONAL_TRAINING),
        PHYSICAL_TRAINING(147, ActivityKind.PHYSICAL_TRAINING),
        TAEKWONDO(148, ActivityKind.TAEKWONDO),
        BOXING(149, ActivityKind.BOXING),
        FREE_SPARRING(150, ActivityKind.FREE_SPARRING),
        KARATE(151, ActivityKind.KARATE),
        FENCING(152, ActivityKind.FENCING),
        BELLY_DANCE(153, ActivityKind.BELLY_DANCE),
        JAZZ_DANCE(154, ActivityKind.JAZZ_DANCE),
        LATIN_DANCE(155, ActivityKind.LATIN_DANCE),
        BALLET(156, ActivityKind.BALLET),
        CORE_TRAINING(157, ActivityKind.CORE_TRAINING),
        BODY_COMBAT(158, ActivityKind.BODY_COMBAT),
        KENDO(159, ActivityKind.KENDO),
        SINGLE_BAR(160, ActivityKind.HORIZONTAL_BAR),
        PARALLEL_BARS(161, ActivityKind.PARALLEL_BARS),
        STREET_DANCE(162, ActivityKind.STREET_DANCE),
        ROLLER_SKATING(163, ActivityKind.ROLLER_SKATING),
        MARTIAL_ARTS(164, ActivityKind.MARTIAL_ARTS),
        PLAZA_DANCING(165, ActivityKind.PLAZA_DANCING),
        TAI_CHI(166, ActivityKind.TAI_CHI),
        DANCE(167, ActivityKind.DANCE),
        HULA_HOOP(168, ActivityKind.HULA_HOOP),
        FRISBEE(169, ActivityKind.FRISBEE),
        DARTS(170, ActivityKind.DARTS),
        ARCHERY(171, ActivityKind.ARCHERY),
        HORSE_RIDING(172, ActivityKind.HORSE_RIDING),
        LASER_TAG(173, ActivityKind.LASER_TAG),
        KITE_FLYING(174, ActivityKind.KITE_FLYING),
        TUG_OF_WAR(175, ActivityKind.TUG_OF_WAR),
        SWINGING(176, ActivityKind.SWING),
        STAIR_CLIMBING(177, ActivityKind.STAIRS),
        OBSTACLE_RACE(178, ActivityKind.OBSTACLE_RACE),
        BILLIARD_POOL(179, ActivityKind.BILLIARD_POOL),
        BOWLING(180, ActivityKind.BOWLING),
        SHUTTLECOCK(181, ActivityKind.SHUTTLECOCK),
        HANDBALL(182, ActivityKind.HANDBALL),
        BASEBALL(183, ActivityKind.BASEBALL),
        SOFTBALL(184, ActivityKind.SOFTBALL),
        CRICKET(185, ActivityKind.CRICKET),
        RUGBY(186, ActivityKind.RUGBY),
        BEACH_SOCCER(187, ActivityKind.BEACH_SOCCER),
        BEACH_VOLLEYBALL(188, ActivityKind.BEACH_VOLLEYBALL),
        GATEBALL(189, ActivityKind.GATEBALL),
        HOCKEY(190, ActivityKind.HOCKEY),
        SQUASH(191, ActivityKind.SQUASH),
        SEPAK_TAKRAW(192, ActivityKind.SEPAK_TAKRAW),
        DODGEBALL(193, ActivityKind.DODGEBALL),
        SAILING(194, ActivityKind.SAILING),
        SURFING(195, ActivityKind.SURFING),
        FISHING(196, ActivityKind.FISHING),
        RAFTING(197, ActivityKind.RAFTING),
        DRAGON_BOATING(198, ActivityKind.DRAGON_BOAT),
        CANOEING(199, ActivityKind.CANOEING),
        ROWING(200, ActivityKind.ROWING),
        WATER_SCOOTER(201, ActivityKind.WATER_SCOOTER),
        STAND_UP_PADDLEBOARDING(202, ActivityKind.STAND_UP_PADDLEBOARDING),
        ICE_SKATING(203, ActivityKind.ICE_SKATING),
        ICE_HOCKEY(204, ActivityKind.ICE_HOCKEY),
        CURLING(205, ActivityKind.CURLING),
        BOBSLEIGH(206, ActivityKind.BOBSLEIGH),
        SLEDDING(207, ActivityKind.SLEDDING),
        BIATHLON(208, ActivityKind.BIATHLON),
        SKATEBOARDING(209, ActivityKind.SKATEBOARDING),
        ROCK_CLIMBING(210, ActivityKind.ROCK_CLIMBING),
        BUNGEE_JUMPING(211, ActivityKind.BUNGEE_JUMPING),
        PARKOUR(212, ActivityKind.PARKOUR),
        BMX(213, ActivityKind.BMX),
        ORIENTEERING(214, ActivityKind.ORIENTEERING),
        PARACHUTING(215, ActivityKind.PARACHUTING),
        MOTOR_AUTO_RACING(216, ActivityKind.AUTO_RACING),
        ESPORTS(223, ActivityKind.ESPORTS),
        PADEL(224, ActivityKind.PADEL),
        OTHER(255, ActivityKind.EXERCISE),
        ;

        private final byte type;
        private final ActivityKind activityKind;

        HuaweiActivityType(final int type, final ActivityKind activityKind) {
            this.type = (byte) type;
            this.activityKind = activityKind;
        }

        public ActivityKind getActivityKind() {
            return activityKind;
        }

        public byte getType() {
            return type;
        }

        public static Optional<HuaweiActivityType> fromByte(final byte type) {
            for (final HuaweiActivityType value : HuaweiActivityType.values()) {
                if (value.getType() == type)
                    return Optional.of(value);
            }
            return Optional.empty();
        }
    }

    public void parseAllWorkouts() {
        parseUnknownWorkoutData();

        try (DBHandler db = GBApplication.acquireDB()) {
            final DaoSession session = db.getDaoSession();
            QueryBuilder<HuaweiWorkoutSummarySample> qb = session.getHuaweiWorkoutSummarySampleDao().queryBuilder();
            for (HuaweiWorkoutSummarySample summary : qb.listLazy()) {
                parseWorkout(session, summary.getWorkoutId(), summary.getDeviceId());
            }
        } catch (Exception e) {
            GB.toast("Exception parsing workouts", Toast.LENGTH_SHORT, GB.ERROR, e);
            LOG.error("Exception parsing workouts", e);
        }
    }

    /**
     * Parses the unknown data from the workout data table
     */
    private static void parseUnknownWorkoutData() {
        try (DBHandler dbHandler = GBApplication.acquireDB()) {
            QueryBuilder<HuaweiWorkoutDataSample> qb = dbHandler.getDaoSession().getHuaweiWorkoutDataSampleDao().queryBuilder().where(
                    HuaweiWorkoutDataSampleDao.Properties.DataErrorHex.notEq("")
            );
            for (HuaweiWorkoutDataSample sample : qb.build().listLazy()) {
                byte[] data = GB.hexStringToByteArray(new String(sample.getDataErrorHex()));
                Workout.WorkoutData.Response response = new Workout.WorkoutData.Response(data);

                for (Workout.WorkoutData.Response.Data responseData : response.dataList) {
                    byte[] dataErrorHex;
                    if (responseData.unknownData == null)
                        dataErrorHex = null;
                    else
                        dataErrorHex = StringUtils.bytesToHex(responseData.unknownData).getBytes(StandardCharsets.UTF_8);

                    HuaweiWorkoutDataSample dataSample = new HuaweiWorkoutDataSample(
                            sample.getWorkoutId(),
                            responseData.timestamp,
                            responseData.heartRate,
                            responseData.speed,
                            responseData.stepRate,
                            responseData.cadence,
                            responseData.stepLength,
                            responseData.groundContactTime,
                            responseData.impact,
                            responseData.swingAngle,
                            responseData.foreFootLanding,
                            responseData.midFootLanding,
                            responseData.backFootLanding,
                            responseData.eversionAngle,
                            responseData.swolf,
                            responseData.strokeRate,
                            dataErrorHex,
                            responseData.calories,
                            responseData.cyclingPower,
                            responseData.frequency,
                            responseData.altitude,
                            responseData.hangTime,
                            responseData.impactHangRate,
                            responseData.rideCadence,
                            responseData.ap,
                            responseData.vo,
                            responseData.gtb,
                            responseData.vr,
                            responseData.ceiling,
                            responseData.temp,
                            responseData.spo2,
                            responseData.cns
                    );

                    dbHandler.getDaoSession().getHuaweiWorkoutDataSampleDao().insertOrReplace(dataSample);
                }
            }
        } catch (Exception e) {
            GB.toast("Exception parsing unknown workout data", Toast.LENGTH_SHORT, GB.ERROR, e);
            LOG.error("Exception parsing unknown workout data", e);
        }
    }

    public static ActivityKind huaweiTypeToGbType(byte huaweiType) {
        final Optional<HuaweiActivityType> type = HuaweiActivityType.fromByte(huaweiType);
        if (type.isPresent())
            return type.get().getActivityKind();
        else
            return ActivityKind.UNKNOWN;
    }

    private String getSwimStyle(byte swimType) {
        return switch (swimType) {
            case 1 -> "breaststroke";
            case 3 -> "butterfly";
            case 4 -> "backstroke";
            case 5 -> "medley";
            default -> "freestyle";
        };
    }

    public void parseWorkout(Long workoutId) {
        LOG.debug("Parsing workout ID {}", workoutId);
        if (workoutId == null)
            return;

        try (DBHandler db = GBApplication.acquireDB()) {
            final DaoSession session = db.getDaoSession();
            final Device device = DBHelper.getDevice(gbDevice, session);
            parseWorkout(session, workoutId, device.getId());
        } catch (Exception e) {
            GB.toast("Exception parsing workout data", Toast.LENGTH_SHORT, GB.ERROR, e);
            LOG.error("Exception parsing workout data", e);
        }
    }

    public void parseWorkout(final DaoSession session, final Long workoutId, final long deviceId) {
        if (workoutId == null)
            return;

        QueryBuilder<HuaweiWorkoutSummarySample> qbSummary = session.getHuaweiWorkoutSummarySampleDao().queryBuilder().where(
                HuaweiWorkoutSummarySampleDao.Properties.WorkoutId.eq(workoutId)
        );
        List<HuaweiWorkoutSummarySample> summarySamples = qbSummary.build().list();
        if (summarySamples.size() != 1)
            return;
        HuaweiWorkoutSummarySample summary = summarySamples.get(0);

        final BaseActivitySummary baseSummary = ActivitySummaryParser.findOrCreateBaseActivitySummary(
                session,
                deviceId,
                summary.getStartTimestamp()
        );

        final List<HuaweiActivityPoint> activityPoints = new ArrayList<>();
        updateBaseSummary(session, summary, baseSummary, activityPoints);

        session.getBaseActivitySummaryDao().insertOrReplace(baseSummary);
    }

    public static Integer parseAndValidatePostureType(final String postureType) {
        if (postureType == null) {
            return null;
        }
        try {
            int type = Integer.parseInt(postureType);
            if (type < 1 || type > 4) {
                return null;
            }
            return type;
        } catch (NumberFormatException e) {
            return null;
        }
    }

    public static class HuaweiAdditionalSummaryParameter {
        public static final int TYPE_STRING = 1;
        public static final int TYPE_INT = 2;
        public static final int TYPE_DOUBLE = 3;
        public static final int TYPE_LONG = 4;

        protected final String activityGroup;
        protected final String activityKey;
        protected final int dataType;
        protected final String unit;

        public HuaweiAdditionalSummaryParameter(String activityGroup, String activityKey, int dataType, String unit) {
            this.activityGroup = activityGroup;
            this.activityKey = activityKey;
            this.dataType = dataType;
            this.unit = unit;
        }


        public void addData(ActivitySummaryData summaryData, final String value) {
            switch (this.dataType) {
                case TYPE_STRING:
                    summaryData.add(this.activityGroup, this.activityKey, value);
                    break;
                case TYPE_INT:
                    summaryData.add(this.activityGroup, this.activityKey, Integer.parseInt(value), this.unit);
                    break;
                case TYPE_DOUBLE:
                    summaryData.add(this.activityGroup, this.activityKey, Double.parseDouble(value), this.unit);
                    break;
                case TYPE_LONG:
                    summaryData.add(this.activityGroup, this.activityKey, Long.parseLong(value), this.unit);
                    break;
            }
        }

    }

    public static class HuaweiWaterTypeAdditionalSummaryParameter extends HuaweiAdditionalSummaryParameter {

        public HuaweiWaterTypeAdditionalSummaryParameter() {
            super(null, ActivitySummaryEntries.WATER_TYPE, HuaweiAdditionalSummaryParameter.TYPE_INT, ActivitySummaryEntries.UNIT_NONE);
        }

        @Override
        public void addData(ActivitySummaryData summaryData, final String value) {
            int val = Integer.parseInt(value);
            summaryData.add(this.activityGroup, this.activityKey, String.format(Locale.ROOT, "type %d", val));
        }
    }

    private static final Map<String, HuaweiAdditionalSummaryParameter> additionalSummaryParameters;

    static {
        additionalSummaryParameters = new HashMap<>();
        // NOTE: next entry contain can invalid data.
        additionalSummaryParameters.put("waterType", new HuaweiWaterTypeAdditionalSummaryParameter());
        //additionalSummaryParameters.put("waterType", new HuaweiAdditionalSummaryParameter(null, ActivitySummaryEntries.WATER_TYPE, HuaweiAdditionalSummaryParameter.TYPE_INT, ActivitySummaryEntries.UNIT_NONE));
        additionalSummaryParameters.put("avgDepth", new HuaweiAdditionalSummaryParameter(null, ActivitySummaryEntries.MAX_DEPTH, HuaweiAdditionalSummaryParameter.TYPE_DOUBLE, ActivitySummaryEntries.UNIT_METERS));
    }

    private void addAdditionalSummaryParameters(ActivitySummaryData summaryData, String key, final String value) {
        HuaweiAdditionalSummaryParameter param = additionalSummaryParameters.get(key);
        if (param == null)
            return;
        param.addData(summaryData, value);
    }

    private static WorkoutChart createRecoveryHeartRateChart(final Context context,
                                                             final List<Entry> heartRateDataPoints) {
        final String label = String.format("%s(%s)", context.getString(R.string.recovery_heart_rate), DefaultWorkoutCharts.getUnitString(context, ActivitySummaryEntries.UNIT_BPM));
        final LineDataSet dataset = DefaultWorkoutCharts.createLineDataSet(context, heartRateDataPoints, label, ContextCompat.getColor(context, R.color.chart_line_heart_rate));
        return new WorkoutChart(
                "recovery_heart_rate",
                context.getString(R.string.recovery_heart_rate),
                ActivitySummaryEntries.GROUP_RECOVERY_HEART_RATE,
                new LineData(dataset),
                null,
                DefaultWorkoutCharts.getUnitString(context, ActivitySummaryEntries.UNIT_BPM)
        );
    }


    public ActivitySummaryData updateBaseSummary(final DaoSession session,
                                                 final HuaweiWorkoutSummarySample summary,
                                                 final BaseActivitySummary baseSummary,
                                                 final List<HuaweiActivityPoint> activityPoints) {

        ActivitySummaryData summaryData = new ActivitySummaryData();
        summaryData.setHasGps(summary.getRawGpsFileLocation() != null);

        try {

            QueryBuilder<HuaweiWorkoutSummaryAdditionalValuesSample> avData = session.getHuaweiWorkoutSummaryAdditionalValuesSampleDao().queryBuilder().where(
                    HuaweiWorkoutSummaryAdditionalValuesSampleDao.Properties.WorkoutId.eq(summary.getWorkoutId())
            );
            List<HuaweiWorkoutSummaryAdditionalValuesSample> summaryAdditionalValuesSamples = avData.build().list();

            QueryBuilder<HuaweiWorkoutDataSample> qbData = session.getHuaweiWorkoutDataSampleDao().queryBuilder().where(
                    HuaweiWorkoutDataSampleDao.Properties.WorkoutId.eq(summary.getWorkoutId())
            );
            List<HuaweiWorkoutDataSample> dataSamples = qbData.build().list();

            QueryBuilder<HuaweiWorkoutPaceSample> qbPace = session.getHuaweiWorkoutPaceSampleDao().queryBuilder().orderAsc(HuaweiWorkoutPaceSampleDao.Properties.PaceIndex).where(
                    HuaweiWorkoutPaceSampleDao.Properties.WorkoutId.eq(summary.getWorkoutId())
            );

            QueryBuilder<HuaweiWorkoutSwimSegmentsSample> qbSegments = session.getHuaweiWorkoutSwimSegmentsSampleDao().queryBuilder().orderAsc(HuaweiWorkoutSwimSegmentsSampleDao.Properties.SegmentIndex).where(
                    HuaweiWorkoutSwimSegmentsSampleDao.Properties.WorkoutId.eq(summary.getWorkoutId())
            );

            Map<String, String> additionalValues = new HashMap<>();

            for (HuaweiWorkoutSummaryAdditionalValuesSample sav : summaryAdditionalValuesSamples) {
                additionalValues.put(sav.getKey(), sav.getValue());
            }

            final ActivityKind type = huaweiTypeToGbType(summary.getType());

            summaryData.add(ActivitySummaryEntries.CALORIES_BURNT, summary.getCalories(), ActivitySummaryEntries.UNIT_KCAL);
            summaryData.add(ActivitySummaryEntries.DISTANCE_METERS, summary.getDistance(), ActivitySummaryEntries.UNIT_METERS);
            if (type == ActivityKind.JUMP_ROPING) {
                summaryData.add(ActivitySummaryEntries.JUMPS, summary.getStepCount(), ActivitySummaryEntries.UNIT_JUMPS);
            } else {
                summaryData.add(ActivitySummaryEntries.STEPS, summary.getStepCount(), ActivitySummaryEntries.UNIT_STEPS);
            }
            summaryData.add(ActivitySummaryEntries.ACTIVE_SECONDS, summary.getDuration(), ActivitySummaryEntries.UNIT_SECONDS);
            //summaryData.add(ActivitySummaryEntries.STATUS, summary.getStatus() & 0xFF, ActivitySummaryEntries.UNIT_NONE);
            summaryData.add(ActivitySummaryEntries.TYPE, summary.getType() & 0xFF, ActivitySummaryEntries.UNIT_NONE);

            if (summary.getDuration() > 0) {
                float totalAvgSpeed = (float) summary.getDistance() / (float) summary.getDuration();
                if (ActivityKind.isSwimActivity(type)) {
                    summaryData.add(ActivitySummaryEntries.PACE_AVG_SECONDS_KM, 100.0f / totalAvgSpeed, ActivitySummaryEntries.UNIT_SECONDS_PER_100_METERS);
                } else if (ActivityKind.isPaceActivity(type)) {
                    summaryData.add(ActivitySummaryEntries.PACE_AVG_SECONDS_KM, (60f / ((totalAvgSpeed * 3.6f))) * 60f, ActivitySummaryEntries.UNIT_SECONDS_PER_KM);
                } else {
                    summaryData.add(ActivitySummaryEntries.SPEED_AVG, totalAvgSpeed, ActivitySummaryEntries.UNIT_METERS_PER_SECOND);
                }
            }

            if (summary.getStrokes() != -1) {
                summaryData.add(ActivitySummaryEntries.STROKES, summary.getStrokes(), ActivitySummaryEntries.UNIT_STROKES);
            }

            if (summary.getPoolLength() != -1) {
                summaryData.add(ActivitySummaryEntries.LANE_LENGTH, summary.getPoolLength(), ActivitySummaryEntries.UNIT_CM);
            }

            if (summary.getLaps() != -1) {
                summaryData.add(ActivitySummaryEntries.LAPS, summary.getLaps(), ActivitySummaryEntries.UNIT_LAPS);
            }

            if (summary.getWorkoutLoad() > 0) {
                summaryData.add(ActivitySummaryEntries.WORKOUT_LOAD, summary.getWorkoutLoad(), ActivitySummaryEntries.UNIT_NONE);
            }

            if (summary.getWorkoutAerobicEffect() > 0) {
                summaryData.add(ActivitySummaryEntries.TRAINING_EFFECT_AEROBIC, summary.getWorkoutAerobicEffect() / 10.0, ActivitySummaryEntries.UNIT_NONE);
            }

            if (summary.getWorkoutAnaerobicEffect() >= 0) {
                summaryData.add(ActivitySummaryEntries.TRAINING_EFFECT_ANAEROBIC, summary.getWorkoutAnaerobicEffect() / 10.0, ActivitySummaryEntries.UNIT_NONE);
            }

            if (summary.getRecoveryTime() > 0) {
                summaryData.add(ActivitySummaryEntries.RECOVERY_TIME, summary.getRecoveryTime() / 60.0, ActivitySummaryEntries.UNIT_HOURS);
            }

            if (summary.getSwimType() != -1) {
                summaryData.add(ActivitySummaryEntries.SWIM_STYLE, getSwimStyle(summary.getSwimType()));
            }

            if (summary.getMaxMET() > 0) {
                int oxygenUptake = (int) (((float) summary.getMaxMET() * 3.5f)) / 65536;
                summaryData.add(ActivitySummaryEntries.MAXIMUM_OXYGEN_UPTAKE, oxygenUptake, ActivitySummaryEntries.UNIT_ML_KG_MIN);
            }

            if (summary.getLongestStreak() > 0) {
                summaryData.add(ActivitySummaryEntries.JUMP_ROPE_LONGEST_STREAK, summary.getLongestStreak(), ActivitySummaryEntries.UNIT_JUMPS);
            }

            if (summary.getTripped() > 0) {
                summaryData.add(ActivitySummaryEntries.JUMP_ROPE_INTERRUPTIONS, summary.getTripped(), ActivitySummaryEntries.UNIT_NONE);
            }


            Integer summaryMinAltitude = summary.getMinAltitude();
            Integer summaryMaxAltitude = summary.getMaxAltitude();
            Integer elevationGain = summary.getElevationGain();
            Integer elevationLoss = summary.getElevationLoss();

            int minHeartRatePeak = summary.getMinHeartRatePeak() & 0xff;
            int maxHeartRatePeak = summary.getMaxHeartRatePeak() & 0xff;

            int avgStrokeRate = summary.getAvgStrokeRate();

            int avgSwolf = summary.getAvgSwolf();

            boolean unknownData = false;
            if (!dataSamples.isEmpty()) {
                int maxSpeed = Integer.MIN_VALUE;
                int speedCount = 0;
                int cadence = 0;
                int cadenceCount = 0;
                int stepLength = 0;
                int stepLengthCount = 0;
                int groundContactTime = 0;
                int groundContactTimeCount = 0;
                int impact = 0;
                int impactCount = 0;
                int maxImpact = 0;
                int swingAngle = 0;
                int swingAngleCount = 0;
                boolean footLandingPresent = false;
                int foreFootLanding = 0;
                int midFootLanding = 0;
                int backFootLanding = 0;
                int eversionAngle = 0;
                int eversionAngleCount = 0;
                int maxEversionAngle = 0;
                int swolf = 0;
                int swolfCount = 0;
                int maxSwolf = 0;
                int minSwolf = 0;
                int strokeRate = 0;
                int strokeRateCount = 0;
                int maxStrokeRate = 0;
                int heartRate = 0;
                int heartRateCount = 0;
                int maxHeartRate = 0;
                int minHeartRate = Integer.MAX_VALUE;
                int sumCalories = 0;
                int minCyclingPower = Integer.MAX_VALUE;
                int maxCyclingPower = 0;
                int cyclingPower = 0;
                int cyclingPowerCount = 0;
                int avgAltitude = 0;
                int altitudeCount = 0;
                int minAltitude = Integer.MAX_VALUE;
                int maxAltitude = Integer.MIN_VALUE;
                Integer previousAlt = null;
                int sumAltitudeUp = 0;
                int sumAltitudeDown = 0;

                int maxFrequency = -1;

                //NOTE: The method of retrieving HR zones from the Huawei watch is not discovered. It may not return zones.
                // So they are calculated based on config.
                HeartRateZones HRZonesCfg = null;

                Integer zonePostureType = parseAndValidatePostureType(additionalValues.get("postureType"));
                LOG.info("Workout HR Zone Workout Posture Type: {}", zonePostureType);
                if (zonePostureType == null) {
                    zonePostureType = HuaweiWorkoutUtils.getHRZonePostureTypeByActivity(type);
                }
                LOG.info("Workout HR Zone Final Posture Type: {}", zonePostureType);

                int zoneCalculateMethod = summary.getHrZoneType();
                LOG.info("Workout HR Zone Calculate Type: {}", zoneCalculateMethod);
                if (zonePostureType != null) {
                    HeartRateZonesSpec spec = gbDevice.getDeviceCoordinator().getHeartRateZonesSpec(gbDevice);
                    HRZonesCfg = HuaweiHeartRateZonesSpec.getHRZonesConfigByPostureAndCalculationMethod(spec.getDeviceConfig(), HuaweiHeartRateZonesSpec.fromHuaweiPostureType(zonePostureType), HuaweiHeartRateZonesSpec.fromHuaweiCalculationMethod(zoneCalculateMethod));
                }

                int dataDelta = 5;
                if (dataSamples.size() >= 2 && dataSamples.get(1).getTimestamp() - dataSamples.get(0).getTimestamp() >= 40) {
                    dataDelta = 60;
                }

                int[] HRZones = new int[5];

                int dataIdx = 0;
                for (HuaweiWorkoutDataSample dataSample : dataSamples) {

                    HuaweiActivityPoint ac = new HuaweiActivityPoint();
                    ac.setTime(new Date(dataSample.getTimestamp() * 1000L));


                    if (HRZonesCfg != null) {
                        int zoneIdx = HuaweiHeartRateZonesSpec.getZoneForHR(dataSample.getHeartRate() & 0xFF, HRZonesCfg);
                        if (zoneIdx != -1 && dataIdx < (dataSamples.size() - 1)) {
                            HRZones[zoneIdx] += dataDelta;
                        }
                        dataIdx++;
                    }

                    if (dataSample.getSpeed() != -1) {
                        speedCount += 1;
                        if (dataSample.getSpeed() > maxSpeed)
                            maxSpeed = dataSample.getSpeed();
                        ac.setSpeed(dataSample.getSpeed() / 10.0f);
                    }
                    //TODO: currently only for walking but I suppose it can be used for all workouts
                    if (summary.getNewSteps() && (type == ActivityKind.WALKING || type == ActivityKind.OUTDOOR_WALKING)) {
                        if (dataSample.getStepRate() != -1) {
                            ac.setCadence(dataSample.getStepRate() & 0xFF);
                        }
                    } else {
                        if (dataSample.getCadence() != -1) {
                            cadence += dataSample.getCadence();
                            cadenceCount += 1;
                            ac.setCadence(dataSample.getCadence());
                        }
                    }
                    if (dataSample.getStepLength() != -1) {
                        stepLength += dataSample.getStepLength();
                        stepLengthCount += 1;
                    }
                    if (dataSample.getGroundContactTime() != -1) {
                        groundContactTime += dataSample.getGroundContactTime();
                        groundContactTimeCount += 1;
                    }
                    if (dataSample.getImpact() != -1) {
                        impact += dataSample.getImpact();
                        impactCount += 1;
                        if (dataSample.getImpact() > maxImpact)
                            maxImpact = dataSample.getImpact();
                    }
                    if (dataSample.getSwingAngle() != -1) {
                        swingAngle += dataSample.getSwingAngle();
                        swingAngleCount += 1;
                    }
                    if (dataSample.getForeFootLanding() != -1) {
                        foreFootLanding += dataSample.getForeFootLanding();
                        footLandingPresent = true;
                    }
                    if (dataSample.getMidFootLanding() != -1) {
                        midFootLanding += dataSample.getMidFootLanding();
                        footLandingPresent = true;
                    }
                    if (dataSample.getBackFootLanding() != -1) {
                        backFootLanding += dataSample.getBackFootLanding();
                        footLandingPresent = true;
                    }
                    if (dataSample.getEversionAngle() != -1) {
                        eversionAngle += dataSample.getEversionAngle();
                        eversionAngleCount += 1;
                        if (dataSample.getEversionAngle() > maxEversionAngle)
                            maxEversionAngle = dataSample.getEversionAngle();
                    }
                    if (dataSample.getSwolf() != -1) {
                        swolf += dataSample.getSwolf();
                        swolfCount += 1;
                        if (dataSample.getSwolf() > maxSwolf)
                            maxSwolf = dataSample.getSwolf();
                        if (dataSample.getSwolf() < minSwolf)
                            minSwolf = dataSample.getSwolf();

                        ac.setSwolf(dataSample.getSwolf());
                    }
                    if (dataSample.getStrokeRate() != -1) {
                        strokeRate += dataSample.getStrokeRate();
                        strokeRateCount += 1;
                        if (dataSample.getStrokeRate() > maxStrokeRate)
                            maxStrokeRate = dataSample.getStrokeRate();
                        ac.setStrokeRate(dataSample.getStrokeRate());
                    }
                    if (dataSample.getHeartRate() != -1 && dataSample.getHeartRate() != 0) {
                        int hr = dataSample.getHeartRate() & 0xff;
                        heartRate += hr;
                        heartRateCount += 1;
                        if (hr > maxHeartRate)
                            maxHeartRate = hr;
                        if (hr < minHeartRate)
                            minHeartRate = hr;

                        ac.setHeartRate(dataSample.getHeartRate() & 0xff);
                    }
                    if (dataSample.getFrequency() != -1) {
                        if (dataSample.getFrequency() > maxFrequency)
                            maxFrequency = dataSample.getFrequency();
                        ac.setFrequency(dataSample.getFrequency());
                    }
                    if (dataSample.getCalories() != -1)
                        sumCalories += dataSample.getCalories();
                    if (dataSample.getCyclingPower() != -1) {
                        int cp = dataSample.getCyclingPower();
                        cyclingPower += cp;
                        cyclingPowerCount += 1;
                        if (cp > maxCyclingPower)
                            maxCyclingPower = cp;
                        if (cp < minCyclingPower)
                            minCyclingPower = cp;
                    }
                    if (dataSample.getAltitude() != null) {
                        int alt = dataSample.getAltitude();
                        avgAltitude += alt;
                        altitudeCount += 1;
                        if (alt > maxAltitude)
                            maxAltitude = alt;
                        if (alt < minAltitude)
                            minAltitude = alt;
                        if (previousAlt != null) {
                            if (alt > previousAlt)
                                sumAltitudeUp += alt - previousAlt;
                            else if (alt < previousAlt)
                                sumAltitudeDown += previousAlt - alt;
                        }
                        previousAlt = alt;

                        ac.setLocation(new GPSCoordinate(0, 0, alt / 10.0f));
                    }
                    if (dataSample.getDataErrorHex() != null)
                        unknownData = true;

                    activityPoints.add(ac);
                }

                if (HRZonesCfg != null) {
                    final double totalTime = Arrays.stream(HRZones).sum();
                    final List<String> zoneOrder = Arrays.asList(ActivitySummaryEntries.HR_ZONE_WARM_UP, ActivitySummaryEntries.HR_ZONE_FAT_BURN, ActivitySummaryEntries.HR_ZONE_AEROBIC, ActivitySummaryEntries.HR_ZONE_ANAEROBIC, ActivitySummaryEntries.HR_ZONE_EXTREME);
                    final int[] zoneColors = new int[]{
                            ContextCompat.getColor(context, R.color.hr_zone_warm_up_color),
                            ContextCompat.getColor(context, R.color.hr_zone_warm_up_color),
                            ContextCompat.getColor(context, R.color.hr_zone_easy_color),
                            ContextCompat.getColor(context, R.color.hr_zone_aerobic_color),
                            ContextCompat.getColor(context, R.color.hr_zone_threshold_color),
                            ContextCompat.getColor(context, R.color.hr_zone_maximum_color),
                    };
                    for (int i = zoneOrder.size() - 1; i >= 0; i--) {
                        double timeInZone = HRZones[i];
                        LOG.info("Zone: {} {}", zoneOrder.get(i), timeInZone);
                        summaryData.add(
                                zoneOrder.get(i),
                                new ActivitySummaryProgressEntry(
                                        timeInZone,
                                        ActivitySummaryEntries.UNIT_SECONDS,
                                        (int) (100 * timeInZone / totalTime),
                                        zoneColors[i]
                                )
                        );
                    }
                }

                // Average the things that should be averaged
                int avgStepRate = (int) ((summary.getStepCount() / ((float) dataSamples.size() * (float) dataDelta)) * 60.0f); // steps per minute

                if (cadenceCount > 0)
                    cadence = cadence / cadenceCount;
                if (stepLengthCount > 0)
                    stepLength = stepLength / stepLengthCount;
                if (groundContactTimeCount > 0)
                    groundContactTime = groundContactTime / groundContactTimeCount;
                if (impactCount > 0)
                    impact = impact / impactCount;
                if (swingAngleCount > 0)
                    swingAngle = swingAngle / swingAngleCount;
                if (eversionAngleCount > 0)
                    eversionAngle = eversionAngle / eversionAngleCount;
                if (swolfCount > 0)
                    swolf = swolf / swolfCount;
                if (strokeRateCount > 0)
                    strokeRate = strokeRate / strokeRateCount;
                if (heartRateCount > 0)
                    heartRate = heartRate / heartRateCount;
                if (cyclingPowerCount > 0)
                    cyclingPower = cyclingPower / cyclingPowerCount;
                if (altitudeCount > 0)
                    avgAltitude = avgAltitude / altitudeCount;

                if (speedCount > 0) {
                    if (ActivityKind.isSwimActivity(type)) {
                        summaryData.add(ActivitySummaryEntries.PACE_MAX, 100.0f / (maxSpeed / 10f), ActivitySummaryEntries.UNIT_SECONDS_PER_100_METERS);
                    } else if (ActivityKind.isPaceActivity(type)) {
                        summaryData.add(ActivitySummaryEntries.PACE_MAX, (60f / ((maxSpeed / 10f) * 3.6)) * 60f, ActivitySummaryEntries.UNIT_SECONDS_PER_KM);
                    } else {
                        summaryData.add(ActivitySummaryEntries.SPEED_MAX, maxSpeed / 10f, ActivitySummaryEntries.UNIT_METERS_PER_SECOND);
                    }
                }

                if (avgStepRate > 0) {
                    if (type == ActivityKind.JUMP_ROPING) {
                        summaryData.add(ActivitySummaryEntries.JUMP_RATE_AVG, avgStepRate, ActivitySummaryEntries.UNIT_JUMPS_PER_MINUTE);
                    } else {
                        summaryData.add(ActivitySummaryEntries.STEP_RATE_AVG, avgStepRate, ActivitySummaryEntries.UNIT_SPM);
                    }
                }

                if (cadenceCount > 0) {
                    summaryData.add(ActivitySummaryEntries.CADENCE_AVG, cadence, ActivitySummaryEntries.UNIT_SPM);
                }

                if (stepLengthCount > 0) {
                    summaryData.add(ActivitySummaryEntries.STEP_LENGTH_AVG, stepLength, ActivitySummaryEntries.UNIT_CM);
                }

                if (groundContactTimeCount > 0) {
                    summaryData.add(ActivitySummaryEntries.GROUND_CONTACT_TIME_AVG, groundContactTime, ActivitySummaryEntries.UNIT_MILLISECONDS);
                }

                if (impactCount > 0) {
                    summaryData.add(ActivitySummaryEntries.IMPACT_AVG, impact, "g");
                    summaryData.add(ActivitySummaryEntries.IMPACT_MAX, maxImpact, "g");
                }

                if (swingAngleCount > 0) {
                    summaryData.add(ActivitySummaryEntries.SWING_ANGLE_AVG, swingAngle, ActivitySummaryEntries.UNIT_DEGREES);
                }

                if (footLandingPresent) {
                    summaryData.add(ActivitySummaryEntries.FORE_FOOT_LANDINGS, foreFootLanding, ActivitySummaryEntries.UNIT_NONE);
                    summaryData.add(ActivitySummaryEntries.MID_FOOT_LANDINGS, midFootLanding, ActivitySummaryEntries.UNIT_NONE);
                    summaryData.add(ActivitySummaryEntries.BACK_FOOT_LANDINGS, backFootLanding, ActivitySummaryEntries.UNIT_NONE);
                }

                if (eversionAngleCount > 0) {
                    summaryData.add(ActivitySummaryEntries.EVERSION_ANGLE_AVG, eversionAngle, ActivitySummaryEntries.UNIT_DEGREES);
                    summaryData.add(ActivitySummaryEntries.EVERSION_ANGLE_MAX, maxEversionAngle, ActivitySummaryEntries.UNIT_DEGREES);
                }

                if (swolfCount > 0) {
                    if (avgSwolf == -1) {
                        avgSwolf = swolf;
                    }
                    summaryData.add(ActivitySummaryEntries.SWOLF_MAX, maxSwolf, ActivitySummaryEntries.UNIT_NONE);
                    summaryData.add(ActivitySummaryEntries.SWOLF_MIN, minSwolf, ActivitySummaryEntries.UNIT_NONE);
                }

                if (strokeRateCount > 0) {
                    if (avgStrokeRate == -1) {
                        avgStrokeRate = strokeRate;
                    }
                    summaryData.add(ActivitySummaryEntries.STROKE_RATE_MAX, maxStrokeRate, ActivitySummaryEntries.UNIT_STROKES_PER_MINUTE);
                }

                if (heartRateCount > 0) {
                    summaryData.add(ActivitySummaryEntries.HR_AVG, heartRate, ActivitySummaryEntries.UNIT_BPM);
                    if (minHeartRatePeak == 0) {
                        minHeartRatePeak = minHeartRate;
                    }

                    if (maxHeartRatePeak == 0) {
                        maxHeartRatePeak = maxHeartRate;
                    }
                }

                if (sumCalories > 0) {
                    summaryData.add(ActivitySummaryEntries.CALORIES_BURNT, sumCalories, ActivitySummaryEntries.UNIT_KCAL);
                }

                if (cyclingPowerCount > 0) {
                    summaryData.add(ActivitySummaryEntries.CYCLING_POWER_AVERAGE, cyclingPower, ActivitySummaryEntries.UNIT_NONE);
                    summaryData.add(ActivitySummaryEntries.CYCLING_POWER_MIN, minCyclingPower, ActivitySummaryEntries.UNIT_NONE);
                    summaryData.add(ActivitySummaryEntries.CYCLING_POWER_MAX, maxCyclingPower, ActivitySummaryEntries.UNIT_NONE);
                }

                if (altitudeCount > 0) {
                    summaryData.add(ActivitySummaryEntries.ALTITUDE_AVG, avgAltitude / 10.0f, ActivitySummaryEntries.UNIT_METERS);

                    if (summaryMinAltitude == null) {
                        summaryMinAltitude = minAltitude;
                    }

                    if (summaryMaxAltitude == null) {
                        summaryMaxAltitude = maxAltitude;
                    }

                    if (elevationGain == null) {
                        elevationGain = sumAltitudeUp;
                    }

                    if (elevationLoss == null) {
                        elevationLoss = sumAltitudeDown;
                    }
                }

                if (maxFrequency > 0) {
                    summaryData.add(ActivitySummaryEntries.JUMP_RATE_MAX, maxFrequency, ActivitySummaryEntries.UNIT_JUMPS_PER_MINUTE);
                }
            }

            if (avgSwolf > 0) {
                summaryData.add(ActivitySummaryEntries.SWOLF_AVG, avgSwolf, ActivitySummaryEntries.UNIT_NONE);
            }

            if (avgStrokeRate > 0) {
                summaryData.add(ActivitySummaryEntries.STROKE_RATE_AVG, avgStrokeRate, ActivitySummaryEntries.UNIT_STROKES_PER_MINUTE);
            }

            if (minHeartRatePeak > 0) {
                summaryData.add(ActivitySummaryEntries.HR_MIN, minHeartRatePeak, ActivitySummaryEntries.UNIT_BPM);
            }
            if (maxHeartRatePeak > 0) {
                summaryData.add(ActivitySummaryEntries.HR_MAX, maxHeartRatePeak, ActivitySummaryEntries.UNIT_BPM);
            }

            if (type == ActivityKind.FREE_DIVING || type == ActivityKind.SCUBA_DIVING) {
                /* BOTH summaryMaxAltitude AND elevationLoss CORRESPOND TO MAX_DEPTH
                if(summaryMaxAltitude != null) {
                    summaryData.add(ActivitySummaryEntries.MAX_DEPTH, summaryMaxAltitude / 10.0f, ActivitySummaryEntries.UNIT_METERS);
                }
                */

                if (elevationLoss != null) {
                    summaryData.add(ActivitySummaryEntries.MAX_DEPTH, elevationLoss / 10.0f, ActivitySummaryEntries.UNIT_METERS);
                }

            } else {
                if (summaryMinAltitude != null) {
                    summaryData.add(ActivitySummaryEntries.ALTITUDE_MIN, summaryMinAltitude / 10.0f, ActivitySummaryEntries.UNIT_METERS);
                }

                if (summaryMaxAltitude != null) {
                    summaryData.add(ActivitySummaryEntries.ALTITUDE_MAX, summaryMaxAltitude / 10.0f, ActivitySummaryEntries.UNIT_METERS);
                }
                if (elevationGain != null) {
                    summaryData.add(ActivitySummaryEntries.ELEVATION_GAIN, elevationGain / 10.0f, ActivitySummaryEntries.UNIT_METERS);
                }
                if (elevationLoss != null) {
                    summaryData.add(ActivitySummaryEntries.ELEVATION_LOSS, elevationLoss / 10.0f, ActivitySummaryEntries.UNIT_METERS);
                }
            }

            final LinkedHashMap<String, ActivitySummaryTableRowEntry> pacesTable = new LinkedHashMap<>();

            pacesTable.put("paces_table",
                    new ActivitySummaryTableRowEntry(
                            ActivitySummaryEntries.GROUP_PACE,
                            Arrays.asList(
                                    new ActivitySummaryValue("#", ActivitySummaryEntries.UNIT_RAW_STRING),
                                    new ActivitySummaryValue("distance"),
                                    new ActivitySummaryValue("Pace")
                            ),
                            true,
                            true
                    )
            );

            final DistanceUnit distanceUnit = GBApplication.getPrefs().getDistanceUnit();

            byte unitType = (byte) (distanceUnit == DistanceUnit.METRIC ? 0 : 1);
            try (CloseableListIterator<HuaweiWorkoutPaceSample> it = qbPace.build().listIterator()) {

                int paceWeightSum = 0;
                int paceSum = 0;
                int paceFastest = Integer.MAX_VALUE;
                int paceSlowest = 0;

                int currentIndex = 1;
                while (it.hasNext()) {
                    int index = it.nextIndex();
                    HuaweiWorkoutPaceSample sample = it.next();

                    if (sample.getType() != unitType)
                        continue;

                    int pace = sample.getPace();

                    int weight = sample.getCorrection() == null ? 10000 : sample.getCorrection();
                    paceWeightSum += weight;
                    paceSum += pace * weight;

                    if (pace < paceFastest)
                        paceFastest = pace;

                    if (pace > paceSlowest)
                        paceSlowest = pace;

                    double distance = sample.getDistance();

                    if (sample.getCorrection() != null) {
                        distance += sample.getCorrection() / 10000d;
                    }

                    final List<ActivitySummaryValue> columns = new LinkedList<>();
                    // TODO: add proper units for type == 1. MILES and SECONDS PER MILE
                    columns.add(new ActivitySummaryValue(currentIndex++, ActivitySummaryEntries.UNIT_NONE));
                    columns.add(new ActivitySummaryValue(distance, ActivitySummaryEntries.UNIT_KILOMETERS));
                    columns.add(new ActivitySummaryValue(sample.getPace(), ActivitySummaryEntries.UNIT_SECONDS_PER_KM));

                    pacesTable.put("paces_table_" + index,
                            new ActivitySummaryTableRowEntry(
                                    ActivitySummaryEntries.GROUP_PACE,
                                    columns,
                                    false,
                                    true
                            )
                    );
                }

                if (paceWeightSum != 0 && paceSum != 0) {
                    summaryData.add(
                            ActivitySummaryEntries.GROUP_PACE,
                            GBApplication.getContext().getString(R.string.fmtPaceAverage),
                            paceSum / paceWeightSum,
                            ActivitySummaryEntries.UNIT_SECONDS_PER_KM
                    );
                }

                if (paceFastest != Integer.MAX_VALUE) {
                    summaryData.add(
                            ActivitySummaryEntries.GROUP_PACE,
                            GBApplication.getContext().getString(R.string.maxPace),
                            paceFastest,
                            ActivitySummaryEntries.UNIT_SECONDS_PER_KM
                    );
                }

                if (paceSlowest != 0) {
                    summaryData.add(
                            ActivitySummaryEntries.GROUP_PACE,
                            GBApplication.getContext().getString(R.string.minPace),
                            paceSlowest,
                            ActivitySummaryEntries.UNIT_SECONDS_PER_KM
                    );
                }

                if (pacesTable.size() > 1) {
                    for (final Map.Entry<String, ActivitySummaryTableRowEntry> e : pacesTable.entrySet()) {
                        summaryData.add(e.getKey(), e.getValue());
                    }
                }
            }

            final LinkedHashMap<String, ActivitySummaryTableRowEntry> segmentsTable = new LinkedHashMap<>();

            try (CloseableListIterator<HuaweiWorkoutSwimSegmentsSample> it = qbSegments.build().listIterator()) {

                int currentIndex = 1;
                int tableIndex = 1;
                while (it.hasNext()) {
                    HuaweiWorkoutSwimSegmentsSample sample = it.next();

                    if (sample.getType() != unitType)
                        continue;

                    // TODO: add proper units for type == 1. imperial

                    final List<ActivitySummaryValue> columns = new LinkedList<>();
                    columns.add(new ActivitySummaryValue(currentIndex++, ActivitySummaryEntries.UNIT_NONE));
                    columns.add(new ActivitySummaryValue(getSwimStyle(sample.getSwimType()), ActivitySummaryEntries.UNIT_NONE));
                    String segmentDistanceUnit = sample.getType() == 1 ? ActivitySummaryEntries.UNIT_YARD : ActivitySummaryEntries.UNIT_METERS;
                    columns.add(new ActivitySummaryValue(sample.getDistance(), segmentDistanceUnit));
                    columns.add(new ActivitySummaryValue(sample.getTime(), ActivitySummaryEntries.UNIT_SECONDS));


                    segmentsTable.put("segments_table_" + tableIndex++,
                            new ActivitySummaryTableRowEntry(
                                    ActivitySummaryEntries.GROUP_PACE,
                                    columns,
                                    true,
                                    true
                            )
                    );

                    final List<ActivitySummaryValue> columns2 = new LinkedList<>();
                    columns2.add(new ActivitySummaryValue("", ActivitySummaryEntries.UNIT_NONE));
                    columns2.add(new ActivitySummaryValue(sample.getStrokes(), ActivitySummaryEntries.UNIT_STROKES));
                    columns2.add(new ActivitySummaryValue(sample.getAvgSwolf(), ActivitySummaryEntries.UNIT_NONE));
                    String segmentPaceUnit = sample.getType() == 1 ? ActivitySummaryEntries.UNIT_SECONDS_PER_100_YARDS : ActivitySummaryEntries.UNIT_SECONDS_PER_100_METERS;
                    columns2.add(new ActivitySummaryValue(sample.getPace(), segmentPaceUnit));


                    segmentsTable.put("segments_table_" + tableIndex++,
                            new ActivitySummaryTableRowEntry(
                                    ActivitySummaryEntries.GROUP_PACE,
                                    columns2,
                                    false,
                                    false
                            )
                    );
                    segmentsTable.put("segments_table_" + tableIndex++,
                            new ActivitySummaryTableRowEntry(
                                    ActivitySummaryEntries.GROUP_PACE,
                                    new ArrayList<>(),
                                    false,
                                    false
                            )
                    );
                }

                if (!segmentsTable.isEmpty()) {
                    for (final Map.Entry<String, ActivitySummaryTableRowEntry> e : segmentsTable.entrySet()) {
                        summaryData.add(e.getKey(), e.getValue());
                    }
                }
            }

            for (Map.Entry<String, String> entry : additionalValues.entrySet()) {
                addAdditionalSummaryParameters(summaryData, entry.getKey(), entry.getValue());
            }

            if (unknownData) {
                summaryData.add(
                        GBApplication.getContext().getString(R.string.unknownDataEncountered),
                        GBApplication.getContext().getString(R.string.yes).toUpperCase()
                );
            }

            if (baseSummary.getName() == null) {
                baseSummary.setName("Workout " + summary.getWorkoutNumber());
            }

            if (baseSummary.getGpxTrack() == null) {
                baseSummary.setGpxTrack(summary.getGpxFileLocation());
            }

            // start time never changes
            baseSummary.setEndTime(new Date(summary.getEndTimestamp() * 1000L));
            baseSummary.setActivityKind(type.getCode());
            baseSummary.setSummaryData(summaryData.toString());
        } catch (Exception e) {
            GB.toast("Exception parsing workout data", Toast.LENGTH_SHORT, GB.ERROR, e);
            LOG.error("Exception parsing workout data", e);
        }
        return summaryData;
    }
}
