//    public ICommand OpenSearchOptionsCommand
    //    {
    //        get { return new Command(OpenSearchOptions); }
    //    }

        public FileTailerViewModel([NotNull] ILogger logger,
            [NotNull] ISchedulerProvider schedulerProvider,
            [NotNull] IFileWatcher fileWatcher,
            [NotNull] ISelectionMonitor selectionMonitor,
            [NotNull] IClipboardHandler clipboardHandler,
            [NotNull] ISearchInfoCollection searchInfoCollection,
            [NotNull] IInlineViewerFactory inlineViewerFactory,
            [NotNull] ISetting<GeneralOptions> generalOptions,
            [NotNull] IRecentSearchCollection recentSearchCollection,
            [NotNull] ISearchMetadataCollection searchMetadataCollection,
            [NotNull] SearchOptionsViewModel searchOptionsViewModel,
            [NotNull] SearchHints searchHints)
        {
            if (logger == null) throw new ArgumentNullException("logger");
            if (schedulerProvider == null) throw new ArgumentNullException("schedulerProvider");
            if (fileWatcher == null) throw new ArgumentNullException("fileWatcher");
            if (selectionMonitor == null) throw new ArgumentNullException("selectionMonitor");
            if (clipboardHandler == null) throw new ArgumentNullException("clipboardHandler");
            if (searchInfoCollection == null) throw new ArgumentNullException("searchInfoCollection");
            if (inlineViewerFactory == null) throw new ArgumentNullException("inlineViewerFactory");
            if (generalOptions == null) throw new ArgumentNullException("generalOptions");
            if (searchMetadataCollection == null) throw new ArgumentNullException("searchMetadataCollection");
            if (searchOptionsViewModel == null) throw new ArgumentNullException("searchOptionsViewModel");
            if (searchHints == null) throw new ArgumentNullException("searchHints");

            SelectionMonitor = selectionMonitor;
            Id = Guid.NewGuid();
            SearchOptions = searchOptionsViewModel;
            SearchHints = searchHints;
            CopyToClipboardCommand = new Command(() => clipboardHandler.WriteToClipboard(selectionMonitor.GetSelectedText()));
            SelectedItemsCount = selectionMonitor.Selected.Connect().QueryWhenChanged(collection => collection.Count).ForBinding();
            SearchCollection = new SearchCollection(searchInfoCollection, schedulerProvider);

            UsingDarkTheme = generalOptions.Value
                    .ObserveOn(schedulerProvider.MainThread)
                    .Select(options => options.Theme == Theme.Dark)
                    .ForBinding();

            HighlightTail = generalOptions.Value
                .ObserveOn(schedulerProvider.MainThread)
                .Select(options => options.HighlightTail)
                .ForBinding();

            HighlightDuration = generalOptions.Value
                .ObserveOn(schedulerProvider.MainThread)
                .Select(options => new Duration(TimeSpan.FromSeconds(options.HighlightDuration)))
                .ForBinding();

            //An observable which acts as a scroll command
            var autoChanged = this.WhenValueChanged(vm => vm.AutoTail);
            var scroller = _userScrollRequested.CombineLatest(autoChanged, (user, auto) =>
            {
                var mode = AutoTail ? ScrollReason.Tail : ScrollReason.User;
                return new ScrollRequest(mode, user.PageSize, user.FirstIndex);
            })
                        .ObserveOn(schedulerProvider.Background)
                        .DistinctUntilChanged();

            IsLoading = searchInfoCollection.All.Take(1).Select(_ => false).StartWith(true).ForBinding();

            //command to add the current search to the tail collection
            AddSearchCommand = new Command(() =>
            {
                var text = SearchHints.SearchText;
                searchInfoCollection.Add(text);
                recentSearchCollection.Add(new RecentSearch(text));
                SearchHints.SearchText = string.Empty;

            }, () => SearchHints.SearchText.IsLongerThanOrEqualTo(3));

            //User feedback to show file size
            FileSizeText = fileWatcher.Latest.Select(fn => fn.Size)
                .Select(size => size.FormatWithAbbreviation())
                .DistinctUntilChanged()
                .ForBinding();

            //User feedback to guide them whilst typing
            SearchHint = this.WhenValueChanged(vm => vm.SearchHints.SearchText)
                            .Select(text =>
                            {
                                if (string.IsNullOrEmpty(text)) return "Type to search";
                                return text.Length < 3 ? "Enter at least 3 characters" : "Hit enter to search";
                            }).ForBinding();

            //tailer is the main object used to tail, scroll and filter in a file
            var lineScroller = new LineScroller(SearchCollection.Latest.ObserveOn(schedulerProvider.Background), scroller);

            //load lines into observable collection
            var lineProxyFactory = new LineProxyFactory(new TextFormatter(searchMetadataCollection));
            var loader = lineScroller.Lines.Connect()
                .Transform(x => lineProxyFactory.Create(x), new ParallelisationOptions(ParallelType.Ordered, 5))
                .Sort(SortExpressionComparer<LineProxy>.Ascending(proxy => proxy))
                .ObserveOn(schedulerProvider.MainThread)
                .Bind(out _data)
                .DisposeMany()
                .Subscribe(changes => logger.Info("Rows changed."+ changes.Adds+" adds, "+changes.Removes+" removed"),
                            ex => logger.Error(ex, "There is a problem with bind data"));

            //monitor matching lines and start index,
            Count = searchInfoCollection.All.Select(latest => latest.Count).ForBinding();
            CountText = searchInfoCollection.All.Select(latest => latest.Count.ToString("##,###")+" lines").ForBinding();
            LatestCount = SearchCollection.Latest.Select(latest => latest.Count).ForBinding();

            //track first visible index
            var firstIndexMonitor = lineScroller.Lines.Connect()
                .Buffer(TimeSpan.FromMilliseconds(250)).FlattenBufferResult()
                .ToCollection()
                .Select(lines => lines.Count == 0 ? 0 : lines.Select(l => l.Index).Max() - lines.Count + 1)
                .ObserveOn(schedulerProvider.MainThread)
                .Subscribe(first => FirstIndex = first);

            //Create objects required for inline viewing
            var isUserDefinedChanged = SearchCollection.WhenValueChanged(sc => sc.Selected)
                .Where(selected => selected != null)
                .Select(selected => selected.IsUserDefined)
                .DistinctUntilChanged()
                .Replay(1)
                .RefCount();

            var inlineViewerVisible = isUserDefinedChanged.CombineLatest(this.WhenValueChanged(vm => vm.ShowInline),
                                                            (userDefined, showInline) => userDefined && showInline);

            CanViewInline = isUserDefinedChanged.ForBinding();
            InlineViewerVisible = inlineViewerVisible.ForBinding();

            //return an empty line provider unless user is viewing inline - this saves needless trips to the file
            var inline = searchInfoCollection.All.CombineLatest(inlineViewerVisible, (index, ud) => ud ? index : new EmptyLineProvider());
            InlineViewer = inlineViewerFactory.Create(inline, this.WhenValueChanged(vm => vm.SelectedItem), lineProxyFactory);

            _cleanUp = new CompositeDisposable(lineScroller,
                loader,
                firstIndexMonitor,
                IsLoading,
                Count,
                LatestCount,
                FileSizeText,
                SearchHint,
                SelectedItemsCount,
                CanViewInline,
                InlineViewer,
                InlineViewerVisible,
                SearchCollection,
                searchInfoCollection,
                HighlightTail,
                UsingDarkTheme,
                searchHints,
                searchMetadataCollection,
                searchMetadataCollection,
                SelectionMonitor,
                SearchOptions,
                Disposable.Create(_userScrollRequested.OnCompleted)
                );
        }
        public InlineViewer([NotNull] InlineViewerArgs args,
            [NotNull] IClipboardHandler clipboardHandler,
            [NotNull] ISchedulerProvider schedulerProvider, 
            [NotNull] ISelectionMonitor selectionMonitor)
        {
            if (args == null) throw new ArgumentNullException("args");
            if (clipboardHandler == null) throw new ArgumentNullException("clipboardHandler");
            if (schedulerProvider == null) throw new ArgumentNullException("schedulerProvider");
            if (selectionMonitor == null) throw new ArgumentNullException("selectionMonitor");
            SelectionMonitor = selectionMonitor;
            CopyToClipboardCommand = new Command(() => clipboardHandler.WriteToClipboard(selectionMonitor.GetSelectedText()));

            _isSettingScrollPosition = false;

            var lineProvider = args.LineProvider;
            var selectedChanged = args.SelectedChanged;
            var pageSize = this.WhenValueChanged(vm=>vm.PageSize);
            var scrollSelected = selectedChanged.Where(proxy => proxy != null)
                    .CombineLatest(lineProvider, pageSize,(proxy, lp,pge) => new ScrollRequest(pge,  proxy.Start));

            var scrollUser = _userScrollRequested
                .Where(x=>!_isSettingScrollPosition)
                .Select(request => new ScrollRequest(ScrollReason.User, request.PageSize, request.FirstIndex));
            
            var scroller= scrollSelected.Merge(scrollUser)
                .ObserveOn(schedulerProvider.Background)
                .DistinctUntilChanged();
            
            var lineScroller = new LineScroller(lineProvider, scroller);
            Count = lineProvider.Select(lp=>lp.Count).ForBinding();
            
            //load lines into observable collection
            var loader = lineScroller.Lines.Connect()
                .Transform(args.LineProxyFactory.Create)
                .Sort(SortExpressionComparer<LineProxy>.Ascending(proxy => proxy))
                .ObserveOn(schedulerProvider.MainThread)
                .Bind(out _data)
                .DisposeMany()
                .Subscribe();

            // track first visible index[required to set scroll extent]
            var firstIndexMonitor = lineScroller.Lines.Connect()
                .Buffer(TimeSpan.FromMilliseconds(250)).FlattenBufferResult()
                .ToCollection()
                .Select(lines => lines.Count == 0 ? 0 : lines.Select(l => l.Index).Max() - lines.Count + 1)
                .ObserveOn(schedulerProvider.MainThread)
                .Subscribe(first =>
                {
                    try
                    {
                        _isSettingScrollPosition = true;
                        FirstIndex = first;
                    }
                    finally
                    {
                        _isSettingScrollPosition = false;
                    }
                });

            _cleanUp = new CompositeDisposable(lineScroller,
                        loader,
                        Count,
                        firstIndexMonitor,
                        SelectionMonitor,
                        Disposable.Create(() =>
                        {
                            _userScrollRequested.OnCompleted();
                        }));
        }