/*
 * The MIT License
 *
 * Copyright © 2014 Filip Plesinger, Juraj Jurco.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * 
 * Authors: 
 * 
 * Filip Plesinger <fplesinger@isibrno.cz>
 * Institute of Scientific Instruments of the ASCR, v. v. i.
 * Brno, Czech Republic
 * 
 * Juraj Jurco <xjuraj@isibrno.cz>
 * Institute of Scientific Instruments of the ASCR, v. v. i.
 * Brno, Czech Republic
 */

package cz.academyofscience.upt.challenge2014.qrs;

import cz.academyofscience.upt.challenge2014.Channel;
import cz.academyofscience.upt.challenge2014.Channels;
import cz.academyofscience.upt.challenge2014.Statistics;
import cz.academyofscience.upt.challenge2014.Utils;
import cz.academyofscience.upt.challenge2014.filters.FFTFilter;
import cz.academyofscience.upt.challenge2014.filters.FIR;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;

/**
 *
 * @author xjuraj
 */
public class QRSFinder {


    private static enum STATISTICS {

        STDEV
    };

    public static enum OUTPUT_TYPE {

        OUTPUT_STREAM_SEL,
        STDOUT_QRS
    };

    private int numSamplesShort = 50;
    private int numSamplesLong = 500;
    private int frequency = -1;
    private int originalFrequency = -1;
    private double freqRatio = -1;
    private final List<SelectionMember> smsPrototype = new ArrayList<>(); //---značky, kde budou odebírány prototypy
    private List<SelectionMember> smsFinal = new ArrayList<>(); //---finální QRS anotace

//    private final List<DescriptiveStatistics> slowEcgStats = null; //-----výsledky směrodatné odchylky z ECG kanálu z dlouhé oblasti
//    private final List<DescriptiveStatistics> fastEcgStats = null; //-----výsledky STDEV z ECG z krátké oblasti

    private double[] slowEcgStd; //-----výsledky směrodatné odchylky z ECG kanálu z dlouhé oblasti
    private double[] fastEcgStd;//-----výsledky STDEV z ECG z krátké oblasti
    private double[] subEcgStd; //----výsledky subSTDEV = slowSTDEV-1.3*fastSTDEV
    private double[] totalCorel; //---výsledky vícekanálové korelace
    private double[] bestCorel; //---výsledky vícekanálové korelace

    private Map<Channel, double[]> averagedShape; //----úschovna pro data [x,kanál z linked channels]
    private int averagedCount = 0; //---počitadlo pro průměrované vzorky

    private int areaTol = -1; //---tolerance šířky peaku v STDEV při vyhledávání míst zájmu
    private int slowStep = -1; //---slowSTDEV skáče po n samplech kvůli rychlosti
    private int fastStep = -1; //---slowSTDEV skáče po n samplech kvůli rychlosti
    private int corelRad = -1; //--radius pro definici tvaru----
    private int corelSize = -1;//corelRad * 2 + 1; //pocita sa neskor v kode...
    private int corelStep = -1;

    private Channel BPChannel = null;
    private int inputDataLength = -1;
    private float recordLengthSec = -1;
    private int usualBeatCount = -1;
    private int minimalBeatCount = -1;
    private int backSamplesFromDiastole = -1;
    private double subCoef = 1.3;

    private float bestCorelTreshold = -1f;
    private float bestCorelNonECGCoefficient = -1f;
    private static final double MINIMAL_INTERBEAT_TIME = 0.25;
    private final boolean DO_DOWNSAMPLING = true;
    private final int DOWNSAMPLE_FREQ = 100;
    
    
    //IIR
    private float resonance = -1;
    private double sharpness = -1;
    //FIR
    private int order = 40;
    private int cutoff = 3;
    private int freq2 = 25;
    
    private Double progress = 0.0;

    public double getProgress(){
        return progress;
    }

    public int getFrequency() {
        return frequency;
    }

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

    /**
     * Convert time in format "hh:mm:ss.mmm" to number of milliseconds
     * @param time
     * @return 
     */
    private long timeToMs(String time){
        time = time.trim();
        String[] msSplit = time.split("\\.");
        String[] tmSplit = msSplit[0].split(":");
        
        long ms = Long.parseLong(msSplit[1]);           //ms to ms
        long sec2ms = (tmSplit.length>0)?1000L*Long.parseLong(tmSplit[tmSplit.length-1]):0;          //seconds to ms
        long min2ms = (tmSplit.length>1)?60L*1000L*Long.parseLong(tmSplit[tmSplit.length-2]):0;      //seconds to ms
        long hrs2ms = (tmSplit.length>2)?60L*60L*1000L*Long.parseLong(tmSplit[tmSplit.length-3]):0;  //seconds to ms
        
        return hrs2ms+min2ms+sec2ms+ms;
    }
    
    
//    private static final Double finalCorelTreshold = 0.9; // práh, který musí překročit celková korelace, aby se začalo hledat maximum
//    private static final Double finalCorelTresholdDown = 0.1; // sestupný práh, do kterého se hledá maximum
    /**
     * determine frequency from data.
     *
     * @param timeChannel time channel to check frequency
     * @return frequency of sampling
     */
    private Integer getFrequency(Channel timeChannel) {
        //Time format: "hh:mm:ss.mmm"
        long first = timeToMs(timeChannel.getData()[0]);
        long last = timeToMs(timeChannel.getData()[timeChannel.getData().length-1]);
        
        float dt = last - first;
        dt /= (float)timeChannel.getData().length;
        
        float freq = 1f / (0.001f*dt);
        return (int)Math.round(freq);
    }

    /**
     * Initial setup of variables
     *
     * @param chs channels with data
     */
    private void setUp(Channels chs) {
        if(chs.getTimeChannel()!=null && frequency == -1){
            frequency = getFrequency(chs.getTimeChannel());
        }
        if(frequency == -1){
            throw new IllegalArgumentException("Frequency is not set!");
        }
        if(originalFrequency == -1){
            originalFrequency = frequency;
        }
        double coef = frequency / 250.0;
        corelRad = (int) Math.max(1, Math.round(80 * coef));
        numSamplesShort = (int) Math.max(1, Math.round(30 * coef));
        numSamplesLong = (int) Math.max(1, Math.round(500 * coef));
        areaTol = (int) Math.max(1, Math.round(30 * coef));
        slowStep = (int) Math.max(1, Math.round(50 * coef));
        fastStep = (int) Math.max(1, Math.round(5 * coef));
        corelStep = (int) Math.max(1, Math.round(2 * coef));
        subCoef = 1.0;
        corelSize = corelRad * 2 + 1;

        inputDataLength = chs.getChannelByPosition(0).values.length;
        recordLengthSec = (float) inputDataLength / frequency;
        usualBeatCount = (int) recordLengthSec;
        minimalBeatCount = usualBeatCount / 4;
        backSamplesFromDiastole = (int) (160f * (frequency / 1000.0));

        BPChannel = chs.getFirstBP();

        bestCorelTreshold = 0.7f;
        bestCorelNonECGCoefficient = 0.8f;
        //IIR
        resonance = 20;
        sharpness = 0.8;
        //FIR
        order = 40;
        cutoff = 3;
        freq2 = 25;
        
//        printVariables();
    }
    
    private void printVariables(){
        System.out.println(" * Frequency: " + frequency);
        System.out.println(" * corelRad: " + corelRad);
        System.out.println(" * numSamplesShort: " + numSamplesShort);
        System.out.println(" * numSamplesLong: " + numSamplesLong);
        System.out.println(" * areaTol: " + areaTol);
        System.out.println(" * slowStep: " + slowStep);
        System.out.println(" * fastStep: " + fastStep);
        System.out.println(" * corelStep: " + corelStep);
        System.out.println(" * corelSize: " + corelSize);
        System.out.println(" * inputDataLength: " + inputDataLength);
        System.out.println(" * recordLengthSec: " + recordLengthSec);
        System.out.println(" * usualBeatCount: " + usualBeatCount);
        System.out.println(" * minimalBeatCount: " + minimalBeatCount);
        System.out.println(" * backSamplesFromDiastole: " + backSamplesFromDiastole);
        System.out.println(" * bestCorelTreshold: " + bestCorelTreshold);
        System.out.println(" * bestCorelNonECGCoefficient: " + bestCorelNonECGCoefficient);
        System.out.println(" * resonance: " + resonance);
        System.out.println(" * sharpness: " + sharpness);
    }

//    public static double[] toPrimitive(Double[] array) {
//        if (array == null) {
//            return null;
//        } else if (array.length == 0) {
//            return new double[0];
//        }
//        final double[] result = new double[array.length];
//        for (int i = 0; i < array.length; i++) {
//            result[i] = array[i];
//        }
//        return result;
//    }
    
    private int alignToMultiplyOf(int value, int  multiplier){
        if(value % multiplier == 0){
            return value;
        } else {
            return ((value / multiplier) + 1)*multiplier;
        }
    }
    
    private double[] getSubarrayStd(Channel ch, int left, int right, int windowSize, int step) {
        int totalSize = alignToMultiplyOf((right - windowSize) - left, step);
        double[] result = new double[totalSize];
        int pos = 0;
        for (int i = left; i < right - windowSize; i += step) {
            double stdev = Statistics.getStdDevSubarray(ch.values, i, i + windowSize);
            for (int j = 0; j < step; j++) { //
                result[pos] = stdev;
                pos++;
            }
        }
        
        return result;
    }

    private List<DescriptiveStatistics> computeWindowStatistics(Channel ch, int left, int right, int windowSize, int step) {
        List<DescriptiveStatistics> result = new ArrayList<>();
        for (int i = left; i < right - windowSize; i += step) {
            double[] data = new double[windowSize];
            System.arraycopy(ch.values, i, data, 0, windowSize);
            DescriptiveStatistics ds = new DescriptiveStatistics(data);
            for (int j = 0; j < step; j++) {
                result.add(ds);
            }
        }
        return result;
    }
//
//    private double[] getStatistic(List<DescriptiveStatistics> statArray, STATISTICS stat) {
//        double[] result = new double[statArray.size()];
//        for (int i = 0; i < statArray.size(); i++) {
//            switch (stat) {
//                case STDEV:
//                    result[i] = statArray.get(i).getStandardDeviation();
//                    break;
//                default:
//                    throw new IllegalArgumentException("Statistic is not defined in switch statement! Used stat: " + stat.name());
//            }
//        }
//        return result;
//    }

    private double[] alignTimeSerie(double[] data, int toSize) {
        //add half part at the beginning
        if (toSize < data.length) {
            throw new IllegalArgumentException(String.format("Cannot align data. "
                    + "Requested size is lower. Data size is: %d and requested size is: %d",
                    data.length, toSize));
        }
        double[] result = new double[toSize];
        int leftAlignSize = (toSize - data.length) / 2;
        for (int i = 0; i < leftAlignSize; i++) {
            result[i] = data[0];
        }
        System.arraycopy(data, 0, result, leftAlignSize, data.length);
        //add the rest to the requested size - avoid rounding in odd numbers: +-1
        double lastVal = data[data.length - 1];
        for (int i = data.length + leftAlignSize; i < toSize; i++) {
            result[i] = lastVal;
        }
        return result;
    }

    private int findByBPChannel(Channels chs) {
        if (BPChannel == null) {
            return -1;
        }
        smsPrototype.clear();
        int blockSize = (int) Math.round(frequency * 2);
        Channel tch = chs.getTimeChannel();
        for (int i = 0; i < inputDataLength - blockSize; i += blockSize) {
            double[] dataBlock = new double[blockSize];
            //int index in source array, array, arrayIdx, count
            System.arraycopy(BPChannel.values, i, dataBlock, 0, blockSize);

            Utils.Pair<Integer, Double> min = Utils.min(dataBlock);
            Utils.Pair<Integer, Double> max = Utils.max(dataBlock);

            if (Math.abs(max.getValue() - min.getValue()) < 20) {
                continue; //bad measurement or patient is not alive
            }
            //kontroluje, ze minimum je naozaj lokalne a nelezi na hraniciach testovaneho pola
            if (min.getKey() > 0 && min.getKey() < blockSize - 1) {
                int pos = i + min.getKey() - backSamplesFromDiastole;
//                System.out.println(pos);
                smsPrototype.add(new SelectionMember(pos, BPChannel.getNumber()));
            }
        }
        if (smsPrototype.size() < minimalBeatCount) {
            // ani tlak nedodal dost prototypů, končíme zpracování tohoto souboru
            return -2;
        }
        return 0;
    }
    
    public void printMarkersIntoOutputStream(List<SelectionMember> marks, OutputStream os, OUTPUT_TYPE[] outType) throws IOException{
        if(marks == null){
            return;
        }
        for (OUTPUT_TYPE ot : outType) {
            switch (ot) {
                case OUTPUT_STREAM_SEL:
                    for (int i = 0; i < marks.size(); i++) {
                        String output = String.format("%d\t%d\t%d\t0\t", i + 1, marks.get(i).getSampleNumber(),
                                marks.get(i).getSampleNumber()) + "%Elapsed time\tshape_corel\n";
                        os.write(output.getBytes("utf-8"));
                    }
                    break;
                case STDOUT_QRS:
                    for (SelectionMember mark : marks) {
                        System.out.println(mark);
                    }
                    break;
                default:
                    throw new IllegalArgumentException(String.format("Output method '%s' is not defined", ot.name()));
            }
        }
    }

    private double[] downsampleData(double[] newValues, Channels chs) {
        frequency = DOWNSAMPLE_FREQ;
        setUp(chs);
        freqRatio = originalFrequency / frequency;
        if(freqRatio == 1.0) return newValues;
        double[] result = new double[(int)Math.ceil(newValues.length / freqRatio)];
        double originalPos = 0.0;
        for (int i = 0; i < result.length; i++) {
            result[i] = newValues[(int)originalPos];
            originalPos += freqRatio;
        }
        return result;
    }

    public List<SelectionMember> findQRS(Channels channels){
        progress = 0.0;
        if(channels.data.isEmpty()){
            //noting to do here
            return new ArrayList<>();
        }
        setUp(channels);
        progress = 1.0;
        //Filter  them (IIR filter)
        long filterTime = System.nanoTime();
        for (Channel channel : channels.data) {
//            channel.setValues(IIR.computeIIR_LP_FAST(channel.getValues(), resonance, sharpness, frequency)); //Apply IIR filter
            double[] newValues = FIR.doFIR(channel.values, FIR.generateLowPass(40, 35, frequency)); 
            
            //downsampling na 100Hz
            if(DO_DOWNSAMPLING){
                newValues = downsampleData(newValues, channels);
            }
            
//            if(channel.isECG){
//                    System.out.println("doing  FFT for: "+channel.getName());
//                    newValues = FFTFilter.fft(newValues, frequency, 15, 35, false, true);
//                }
            channel.values = newValues;
        }
        progress = 13.0;
        
        
        //add corelSize values at the begining and at the end. 
        //First value is copied at the beginning and last value added to the end
        for (Channel channel : channels.data) {
            double[] values = alignTimeSerie(channel.values, channel.values.length + corelSize * 2);
            channel.values = values;
////////            double lastVal = values[values.length - 1];
////////            double firstVal = values[0];
////////            
////////            for (int i = 0; i < corelSize; i++) {
////////                //na koniec sa do vsetkych kanalov prida posledna hodnota
////////                values.add(lastVal);
////////                values.add(0, firstVal);
////////            }
        }

        progress = 14.0;
        //prvy a posledny index
        int left = 0;                                                           //inclusive
        int right = channels.data.get(0).values.length;               //exclusive
        List<Channel> ecgChannels = channels.getAllEcgChannels();
        progress = 15.0;
        for (Channel ecg : ecgChannels) {
            findPreliminaryAnotations(channels, ecg, left, right);
            if (smsPrototype.size() >= minimalBeatCount) {
                break;
            }
        }
        progress = 35.0;
        if (smsPrototype.size() < minimalBeatCount) {
            //find preliminary anotations by BP channel
            if(findByBPChannel(channels) < 0){
                //nothing found, terminating computation
                return new ArrayList<>();
            }
            
        }

        getAveragedShape(channels);
        progress = 50.0;
        List<Shape> shapes = defineShapes(channels);
        progress = 60.0;
        normalizeWeights(channels, shapes);
        progress = 70.0;
        channelsResponse(channels, shapes);
        progress = 80.0;
        makeFinalMarks(channels, shapes);
        progress = 90.0;

        for (Channel ch : channels.data) {
            ch.values = Arrays.copyOfRange(ch.values, corelSize, ch.values.length);
            //ch.setValues(ch.getValues().subList(corelSize, ch.getValues().length));
        }
        progress = 100.0;
        return smsFinal;
//        totalCorel = Arrays.copyOfRange(totalCorel, corelSize, totalCorel.length);
//        slowEcgStd = Arrays.copyOfRange(slowEcgStd, corelSize, slowEcgStd.length);
//        fastEcgStd = Arrays.copyOfRange(fastEcgStd, corelSize, fastEcgStd.length);
//        subEcgStd = Arrays.copyOfRange(subEcgStd, corelSize, subEcgStd.length);
//        bestCorel = Arrays.copyOfRange(bestCorel, corelSize, bestCorel.length);

    }

    private void findPreliminaryAnotations(Channels channels, Channel ecg, int left, int right) {
        //---------------------------------- ECG směrodatná odchylka z malého počtu samplů--------------------------------------
//        fastEcgStats = computeWindowStatistics(ecg, left, right, numSamplesShort, 1);
//        fastEcgStd = getStatistic(fastEcgStats, STATISTICS.STDEV);
        fastEcgStd = getSubarrayStd(ecg, left, right, numSamplesShort, fastStep);
        fastEcgStd = alignTimeSerie(fastEcgStd, fastEcgStd.length+numSamplesShort);

        //---------------------------------- ECG směrodatná odchylka z velkého počtu samplů--------------------------------------
//        slowEcgStats = computeWindowStatistics(ecg, left, right, numSamplesLong, slowStep);
//        slowEcgStd = getStatistic(slowEcgStats, STATISTICS.STDEV);
        slowEcgStd = getSubarrayStd(ecg, left, right, numSamplesLong, slowStep);
        slowEcgStd = alignTimeSerie(slowEcgStd, slowEcgStd.length+numSamplesLong);

        //-----------------------------------  sestavení kanálu s rozdílem ---------------------------
        subEcgStd = new double[right];
//            System.out.println(String.format("Fast: %d Slow: %d Right: %d", fastEcgStd.size(), slowEcgStd.size(), right));
        for (int i = 0; i < right; i++) {
            if(i<fastEcgStd.length && i < slowEcgStd.length){
                subEcgStd[i] = fastEcgStd[i] - subCoef * slowEcgStd[i];
            } else {
                subEcgStd[i] = -1;
            }
        }
        //-------------------------Vyhledani mist zajmu-----------------------------
        placesOfInterest(channels, right);
    }

    private void makeFinalMarks(Channels channels, List<Shape> shapes) {
        //------- tvorba finálních značek na základě korelačního kanálu -------------------
        smsFinal = new ArrayList<>();

        //---NOVY---- automatické určení tresholdu korelace ----
        Double autoTreshUp = 0.0;

        for (Shape shape : shapes) {
            if (shape.weight <= 0.1) {
                continue;
            }
            autoTreshUp += shape.weight / 2.5;// 
        }

        Double autoTreshDown = autoTreshUp / 8.0;

//            Debug.Print("Treshold Up=" + autoTreshUp);
        //----ZMENA --- autotreshUp & down----
        // float lastPosSec = -10000; //---pozice poslední QRS detekce v sekundách
        int lastMaxPos = -100;
        double lastPosSec = -10000.0;
        double actPosSec = -10000.0;
        for (int i = 0; i < totalCorel.length; i++) {
            if (totalCorel[i] < autoTreshUp && bestCorel[i]<bestCorelTreshold) {
                continue;
            }
            //---hodnota překročila finalCorelTreshold a následuje vyhledání maxima
            Double max = totalCorel[i];
            int maxPos = i;
            while (i < totalCorel.length && totalCorel[i] >= autoTreshDown) //---iteruji, dokud hodnota opět neklesne pod treshold
            {
                if (totalCorel[i] > max) {
                    max = totalCorel[i];
                    maxPos = i;
                }
                i++;
            }
            //----NOVE---
//            if (smsFinal.size() > 0) {
//                lastPosSec = (double) smsFinal.get(smsFinal.size() - 1).getSampleNumber() / (double) frequency;
//            }
            actPosSec = (double) maxPos / (double) frequency;
            if (actPosSec - lastPosSec < MINIMAL_INTERBEAT_TIME) {
//                System.out.println(String.format("Mark excluded at: %f because last was at position: %f", actPosSec, lastPosSec));
//                Debug.Print("MARK Edcluded");
                continue;  //---- kontrola na vzdálenost od poslední detekovaného QRS
            }
            lastPosSec = actPosSec;
            //----vygenerování nové značky-----
            Channel tch = channels.getChannelByPosition(0);
            /**
             * -corelSize replaces this... for (int s = 0; s < smsFinal.Count;
             * s++) { smsFinal[s].leftIndex -= corelSize; smsFinal[s].rightIndex
             * -= corelSize; }
             */
            int actualizedPos = maxPos - corelSize;
            if ((actualizedPos >= 0) && (actualizedPos < tch.values.length)) {
                int finalPos = actualizedPos;
                if(DO_DOWNSAMPLING){
                    finalPos = (int)(actualizedPos * freqRatio);
                }
                SelectionMember sm = new SelectionMember(finalPos, 0);
                sm.setTimeChannel(channels.getTimeChannel());
                smsFinal.add(sm);
                lastMaxPos = maxPos;
            } else {
//                System.err.println("Skipping mark at position: " + maxPos + " of " + tch.getData().length);
            }
        }
    }

    private void channelsResponse(Channels channels, List<Shape> shapes) {
        //---------------------vytvořit odezvu vícekanálové korelace----------------------------
        totalCorel = new double[inputDataLength];
        bestCorel = new double[inputDataLength];

        int totalCorelPos = 0;
        for (int i = 0; i < corelRad * 2; i++) { //---doplnění mezer na začátku
            totalCorel[totalCorelPos] = 0.0;
            bestCorel[totalCorelPos] = 0.0;
            totalCorelPos++;
        }

        int lastPerc = 0;
        for (int i = corelRad; i < inputDataLength - corelRad; i += corelStep) {
            double corelation = 0.0;
            int pos = i;
            double bestCor = 0;

            for (int c = 0; c < channels.data.size(); c++) {
                Channel ch = channels.data.get(c);
                Shape sh = shapes.get(c);
                if (sh.weight <= 0.1) {
                    continue;
                }
                if (pos + corelSize >= ch.values.length) {
                    continue;
                }
                double[] testData = Arrays.copyOfRange(ch.values, pos, pos + corelSize);
                double parCorel = Statistics.correlation(testData, sh.data);
                double weight = sh.weight;
                //corelation += corr * shapes.get(c).getWeight();

                corelation += parCorel * weight;

                double knst = 1;
                if (!ch.isECG) {
                    knst = bestCorelNonECGCoefficient;
                }

                if (parCorel * weight * knst > bestCor) {
                    bestCor = parCorel * weight * knst;

                }
            }
            for (int j = 0; j < corelStep && totalCorelPos < totalCorel.length; j++) {
                totalCorel[totalCorelPos] = corelation;
                bestCorel[totalCorelPos] = bestCor;
                totalCorelPos++;
            }
        }
    }

    private boolean isECGChannel(Channel ch) {
        return ch.isECG;
    }

    private void normalizeWeights(Channels channels, List<Shape> shapes) {
        //-------------------normalizace vah ----
        for (int w = 0; w < channels.data.size(); w++) {
            Shape s = shapes.get(w);
            s.weight /= (double) smsPrototype.size();
        }
    }

    private List<Shape> defineShapes(Channels channels) {
        //------vyšetření vah pomocí korelace průměrných tvarů na předběžné značky sběru prototypů smsPrototype ------
        List<Shape> shapes = new ArrayList<>();
        for (Channel channel : channels.data) {
            Shape shp = new Shape();
            shp.weight = 0.0;
            shp.data= new double[corelSize];
            System.arraycopy(averagedShape.get(channel), 0, shp.data, 0, corelSize);
            shapes.add(shp);
        }
        for (SelectionMember smsProto : smsPrototype) {
            int pos = smsProto.getSampleNumber() - corelRad;//getLeftIndex() - corelRad;
            for (int c = 0; c < channels.data.size(); c++) {
                Shape s = shapes.get(c);
                double[] testData = Arrays.copyOfRange(channels.data.get(c).values,
                        pos, pos + corelSize);
                double corr = Statistics.correlation(testData, s.data);
                s.weight += corr;
            }
        }
        return shapes;
    }

    private void placesOfInterest(Channels channels, int right) {
        //---------------------vyhlednání míst zájmu-------------------
        int numSamplesUp = 0; //-počet po sobě jdoucích samplů >0
        smsPrototype.clear();
        Channel tch = channels.getTimeChannel();

        for (int i = numSamplesLong; i < right - numSamplesLong; i++) {
            if (subEcgStd[i] < 0) {
                if (numSamplesUp > numSamplesShort - areaTol && numSamplesUp < numSamplesShort + areaTol) {
                    int pos = i - numSamplesUp / 2;
                    smsPrototype.add(new SelectionMember(pos, 0));//new SelectionMember(pos, pos, "ShapeFinder"));
                }
                numSamplesUp = 0;
                continue;
            }
            numSamplesUp++;
        }
    }

    private void getAveragedShape(Channels channels) {
        //-----------Definice průměrných tvarů----------
        averagedShape = new TreeMap<>();
        for (Channel channel : channels.data) {
            double[] aShape = new double[corelSize];
            averagedShape.put(channel, aShape);
        }
        averagedCount = 0;

        for (SelectionMember smsProt : smsPrototype) {
            //for all found marks
            averagedCount++;
            Double newPartRatio = 1.0 / averagedCount;
            Double oldPartRatio = 1.0 - newPartRatio;
            int startPos = smsProt.getSampleNumber() - corelRad;//getLeftIndex() - corelRad;
            for (Channel channel : channels.data) {
                double[] aShape = averagedShape.get(channel);
                for (int i = 0; i < corelSize; i++) {
                    int sourceIndex = i + startPos;
                    aShape[i] = aShape[i] * oldPartRatio + channel.values[sourceIndex] * newPartRatio;
                }
            }
        }
    }

    
}
