/// <summary>
        /// Averages adjacent entries such that the result has only one entry per time bin of the specified width
        /// </summary>
        public static IArrayView<EEGDataEntry> DownSample(this IEnumerable<EEGDataEntry> entries, int binWidthMillis)
        {
            var bins = entries.BinByTime(binWidthMillis);
            var downSampled = Arrays.New<EEGDataEntry>(bins.Count);
            for (int i = 0; i < bins.Count; i++)
            {
                if (bins[i].Count == 0)
                {
                    var baseEntry = downSampled[i - 1];
                    downSampled[i] = new EEGDataEntry(baseEntry.Marker, baseEntry.TimeStamp + binWidthMillis, baseEntry.RelativeTimeStamp + binWidthMillis, baseEntry.Data);
                }
                else
                    downSampled[i] = bins[i].AverageEntry();
            }

            return downSampled;
        }
        private Example GetExample(IArrayView<EEGDataEntry> trial)
        {
            int marker = trial.FirstItem().Marker;

            var means = Channels.Values.Select(ch => trial.Channel(ch).Average()).ToArray();

            IArrayView<EEGDataEntry> trimmedTrial = trial.DownSample(this.Settings.BinWidthMillis);
            if (trial.Count > this.minFeatures)
                trimmedTrial = trial.SubView(0, this.minFeatures);
            else if (trial.Count < this.minFeatures)
            {
                var defaultEntry = new EEGDataEntry(marker, 0, 0, means);
                trimmedTrial = Arrays.FromMap(i => i < trial.Count ? trial[i] : defaultEntry, this.minFeatures);
            }

            var features = trimmedTrial
                .Select(this.selectedBins)
                .SelectMany(e => this.Settings.SelectedChannels.Select(ch => e[ch] - means[ch.ToIndex()]));
            if (this.Settings.IncludeChannelMeans)
                features = features.Concat(this.Settings.SelectedChannels.Select(ch => means[ch.ToIndex()]));

            return new Example(marker, features);
        }
        private void BuildView()
        {
            this.SuspendLayout();
            var tabs = new CustomTabControl() { Dock = DockStyle.Fill };
            tabs.DisplayStyleProvider = new TabStyleVisualStudioProvider(tabs) { ShowTabCloser = true };
            tabs.TabClosing += (sender, args) => ((CustomTab)args.TabPage).RaiseClosingSafe(args);

            var startTab = new CustomTab() { Text = "Classifiers " }; // the ending space is necessary for some reason
            startTab.Closing += (sender, args) =>
            {
                args.Cancel = true;
                if (GUIUtils.IsUserSure("Reset classifiers?"))
                {
                    this.classifierTabs.Clear();
                    this.Controls.Remove(tabs);
                    tabs.Dispose();
                    this.BuildView();
                    this.OnSizeChanged(EventArgs.Empty);
                }
            };

            // classifier list
            var classifierList = new CheckedListBox() { Dock = DockStyle.Fill, CheckOnClick = true };
            classifierList.AddContextMenu();
            Action<ClassificationScheme> addClassifier = (scheme) =>
            {
                // get unique name if necessary
                string baseName = string.IsNullOrWhiteSpace(scheme.Settings.Name)
                    ? "new classifier"
                    : scheme.Settings.Name;
                if (!this.classifierTabs.Select(ct => ct.Text).Contains(baseName))
                    scheme.Settings.Name = baseName;
                else
                {
                    int i = 1;
                    while (this.classifierTabs
                        .Select(ct => ct.Text.ToLower())
                        .Contains(string.Format("{0} {1}", baseName, i)))
                        i++;
                    scheme.Settings.Name = string.Format("{0} {1}", baseName, i);
                }

                // create the tab
                var classifierTab = new ClassificationSchemeTab(scheme);
                classifierTab.TextChanged += (sender, args) => classifierList.Invalidate();
                classifierTab.Closing += (sender, args) =>
                {
                    this.classifierTabs.Remove(classifierTab);
                    classifierList.Items.Remove(classifierTab);
                };

                this.classifierTabs.Add(classifierTab);
                tabs.TabPages.Add(classifierTab);
                classifierList.Items.Add(classifierTab, true);
            };
            this.getSelectedClassifiers = () => classifierList.CheckedItems.Cast<ClassificationSchemeTab>().Select(cst => cst.ClassificationScheme).ToIArray();

            // buttons
            var buttonTable = GUIUtils.CreateButtonTable(Direction.Horizontal, DockStyle.Bottom,
            GUIUtils.CreateFlatButton("New", (b) =>
            {
                var classifier = classifierList.Items.Count > 0
                    ? ((ClassificationSchemeTab)(classifierList.SelectedItem ?? classifierList.Items[0])).ClassificationScheme
                    : new ClassificationScheme();

                classifier.Settings.Name = string.Empty;
                addClassifier(classifier);
            }, startTab.ToolTip, "Create a new classifier"),
            GUIUtils.CreateFlatButton("Load", (b) =>
            {
                if (this.openDialog.ShowDialog() != DialogResult.OK)
                    return;

                ClassificationScheme scheme;
                foreach (var path in this.openDialog.FileNames)
                {
                    if (Utils.TryDeserializeFile(this.openDialog.FileName, out scheme))
                        addClassifier(scheme);
                    else
                        GUIUtils.Alert("Failed to load classifier info from " + path, MessageBoxIcon.Error);
                }
            }, startTab.ToolTip, "Load a previously saved classifier settings file"));

            // artifact detection config
            var artifactDetectionPanel = new ConfigurationPanel(this.artifactDetection);
            artifactDetectionPanel.PropertyChanged += args => this.artifactDetection.SetProperty(args.Property, args.Getter());

            // artifact detection label
            var artifactDetectionLabel = new Label() { Dock = DockStyle.Bottom, TextAlign = ContentAlignment.MiddleCenter, Visible = false };
            IEnumerable<EEGDataEntry> empty = new EEGDataEntry[0], entries = empty;
            var listener = new EEGDataListener(GUIUtils.GUIInvoker,
                source => artifactDetectionLabel.Visible = true,
                data =>
                {
                    if (!this.artifactDetection.UseArtifactDetection)
                    {
                        artifactDetectionLabel.Visible = false;
                        entries = empty;
                        return;
                    }

                    artifactDetectionLabel.Visible = true;
                    entries = entries.Concat(data);
                    if (data.LastItem().TimeStamp - entries.First().TimeStamp >= 500)
                    {
                        if (this.artifactDetection.HasMotionArtifact(entries))
                        {
                            artifactDetectionLabel.Text = "Motion artifact detected!";
                            artifactDetectionLabel.BackColor = Color.Red;
                            artifactDetectionLabel.ForeColor = Color.White;
                            if (this.artifactDetection.Beep)
                                GUIUtils.GUIInvoker.BeginInvoke(SystemSounds.Beep.Play);
                        }
                        else
                        {
                            artifactDetectionLabel.Text = "No artifacts detected";
                            artifactDetectionLabel.BackColor = Color.Green;
                            artifactDetectionLabel.ForeColor = Color.Black;
                        }

                        entries = empty;
                    }
                },
                source => artifactDetectionLabel.Visible = false);
            // avoid using the gui invoker before the handle has been created
            this.HandleCreated += (sender, args) => EmotivDataSource.Instance.AddListener(listener);
            artifactDetectionLabel.Disposed += (sender, args) => { EmotivDataSource.Instance.RemoveListener(listener); listener.Dispose(); };

            // right half
            var rightPanel = new Panel() { Dock = DockStyle.Fill };
            rightPanel.Controls.Add(classifierList);
            rightPanel.Controls.Add(buttonTable);

            // left half
            var leftPanel = new Panel() { Dock = DockStyle.Fill };
            leftPanel.Controls.Add(artifactDetectionPanel);
            leftPanel.Controls.Add(artifactDetectionLabel);

            var cols = GUIUtils.CreateTable(new double[] { .5, .5 }, Direction.Horizontal);
            cols.Controls.Add(rightPanel, 0, 0);
            cols.Controls.Add(leftPanel, 1, 0);
            startTab.Controls.Add(cols);

            tabs.TabPages.Add(startTab);
            this.Controls.Add(tabs);
            this.ResumeLayout(false);
        }