public void Generate(
            WsdProject project, GenerationInfo info, IProgressHandle progress)
        {
            if (info == null)
            {
                throw new ArgumentNullException(nameof(info));
            }

            if (PathEx.Identify(info.DestinationFolder) != PathIdentity.Directory ||
                Directory.GetFiles(info.DestinationFolder, "*", SearchOption.AllDirectories).Length > 0)
            {
                throw new ArgumentException("DestinationFolder must be an empty existing directory.");
            }

            info.AssertIsValid();

            var handlers = _dataGenerationHandlers
                           .OrderBy(x => x.GetExecutionPriority(project))
                           .ToArray();

            foreach (var handler in handlers)
            {
                handler.BeforeGenerationStarted(project, info, progress);
            }

            var reorderedDictionary = _classDeterminator.GetReorderedDictionary(project, info, progress);

            foreach (var handler in handlers)
            {
                handler.AfterDictionaryReordered(reorderedDictionary, project, info, progress);
            }

            var dataSets = new Dictionary <DataSetName, DataSetByText>
            {
                [DataSetName.Train] = new DataSetByText(
                    DataSetName.Train,
                    _generationAlgorithm.GenerateRecords(project.TrainData, project, info, progress)),

                [DataSetName.Test] = new DataSetByText(
                    DataSetName.Test,
                    _generationAlgorithm.GenerateRecords(project.TestData, project, info, progress))
            };

            foreach (var handler in handlers)
            {
                handler.AfterRecordsGenerated(dataSets, project, info, progress);
            }

            var dataSetGroups = _dataSetGrouper.FormGroups(dataSets, project, info, progress);

            foreach (var handler in handlers)
            {
                handler.AfterGroupsFormed(dataSetGroups, project, info, progress);
            }

            _testOnlySetExtractor.Extract(dataSetGroups, project, info, progress);

            foreach (var handler in handlers)
            {
                handler.AfterTestOnlySetExtracted(dataSetGroups, project, info, progress);
            }

            if (info.ExtractValidationSet)
            {
                _validationSetExtractor.Extract(dataSetGroups, info, progress);

                foreach (var handler in handlers)
                {
                    handler.AfterValidationSetExtracted(dataSetGroups, project, info, progress);
                }
            }

            if (info.ShuffleData)
            {
                _dataSetShuffler.ShuffleData(dataSetGroups, progress);

                foreach (var handler in handlers)
                {
                    handler.AfterDataShuffled(dataSetGroups, project, info, progress);
                }
            }

            var context = new FeatureSelectionContext
            {
                GenerationInfo      = info,
                ReorderedDictionary = reorderedDictionary,
                FilteredPosList     = new WsdPosList(info.FilteredPosList),
                Project             = project
            };

            foreach (var handler in handlers)
            {
                handler.BeforeDataWritten(dataSetGroups, project, info, progress);
            }

            _dataSetWriter.WriteData(info.DestinationFolder, dataSetGroups, context, progress);

            SystemJsonWriter.Write(
                Path.Combine(
                    info.DestinationFolder,
                    FileName.GenerationInfo + FileExtension.WsdGenInfo),
                info);

            SystemJsonWriter.Write(
                Path.Combine(
                    info.DestinationFolder,
                    FileName.GenerationInfo + FileExtension.Text),
                new GenerationInfoReadable(info),
                null, false);

            foreach (var handler in handlers)
            {
                handler.AfterGenerationCompleted(project, info, progress);
            }
        }