function FileSearch(opts) {
    bindEvents(this);

    this.logger = getLogger('FileSearch');
    this.req = null;
    this.initializedWithSearch = opts.inited_with_search;
    this.collectionName = opts.collection_name;
    this.baseUrl = opts.base_url;
    this.inputQuery = opts.query || null;
    this.resultsCount = opts.count || 0;
    this.resultsOffset = opts.query ? opts.per_page : 0;
    this.resultsPerPage = opts.per_page;
    this.minQueryLength = opts.min_query_length;

    this.refs = {
        container: ge(opts.container),
        filesList: ge('files_list'),
        clearIcon: ge('files_search_clear_icon'),
        showMore: ge('files_show_more'),
        input: ge('files_search_input'),
        info: ge('files_search_info'),
        infoResultsCount: ge('files_search_info_text')
    };

    if (this.inputQuery)
        this.refs.input.value = this.inputQuery;

    this.init();
}

extend(FileSearch.prototype, {
    init: function() {
        this.refs.clearIcon.addEventListener('click', this.onClearIconClick);
        this.refs.input.addEventListener('focus', this.onSearchInputFocus);
        this.refs.input.addEventListener('blur', this.onSearchInputBlur);
        this.refs.input.addEventListener('input', this.onSearchInput);
        this.refs.input.addEventListener('keydown', this.onSearchInputKeyDown);
        this.refs.showMore.addEventListener('click', this.onShowMoreClick);

        this.searchThrottled = throttle(this.search, 300);

        this.refs.input.focus();
    },

    abortRequestIfNeeded: function() {
        if (this.req) {
            this.req.abort();
            this.req = null;
        }
    },

    search: function(query) {
        var queryChanged = this.inputQuery !== query;
        if (queryChanged) {
            this.resultsOffset = 0;
            this.resultsCount = 0;
        }

        this.abortRequestIfNeeded();
        this.inputQuery = query;

        this.showProgress();

        this.req = ajax.get(this.baseUrl, {
            q: query,
            offset: this.resultsOffset
        }, function(error, response) {
            this.hideProgress();
            if (error) {
                alert('error!');
                console.error(error);
            } else {
                if (!this.initializedWithSearch) {
                    var filesListEl = this.refs.filesList;
                    var filesListHiddenEl = ge('files_list_hidden');
                    if (!filesListHiddenEl.hasAttribute('data-non-empty')) {
                        filesListHiddenEl.innerHTML = filesListEl.innerHTML;
                        filesListHiddenEl.setAttribute('data-non-empty', '1');
                        filesListEl.innerHTML = '';
                    }
                }

                if (queryChanged) {
                    this.refs.filesList.innerHTML = response.html
                } else {
                    this.refs.filesList.innerHTML += response.html;
                }

                this.resultsCount = response.search_count;
                this.resultsOffset = response.new_offset;

                this.updateLocation();
                this.showResultsCount();

                if (this.canLoadMore()) {
                    show(this.refs.showMore)
                } else {
                    hide(this.refs.showMore)
                }
            }
        }.bind(this));
    },

    canLoadMore: function() {
        return this.resultsOffset < this.resultsCount;
    },

    showProgress: function () {
        addClass(this.refs.showMore, 'is-loading');

        if (!visible(this.refs.info))
            slideutils.down(this.refs.info);

        addClass(this.refs.info, 'is-loading');
        show(this.refs.clearIcon);
    },

    hideProgress: function() {
        removeClass(this.refs.showMore, 'is-loading');
        removeClass(this.refs.info, 'is-loading');
    },

    hideSearch: function() {
        hide(this.refs.showMore);

        var filesListEl = ge('files_list');
        var filesListHiddenEl = ge('files_list_hidden');
        var nonEmpty = filesListHiddenEl.getAttribute('data-non-empty') === '1';
        if (!nonEmpty)
            window.location = this.baseUrl;
        else {
            filesListEl.innerHTML = filesListHiddenEl.innerHTML
            filesListHiddenEl.innerHTML = ''
            filesListHiddenEl.removeAttribute('data-non-empty')
        }

        this.abortRequestIfNeeded();

        this.inputQuery = null;
        this.resultsCount = 0;
        this.resultsOffset = 0;

        this.updateLocation();

        hide(this.refs.clearIcon);
        this.hideProgress();
        if (visible(this.refs.info))
            slideutils.up(this.refs.info);
    },

    updateLocation: function() {
        var title = lang('4in1') + ' - ' + lang('files_'+this.collectionName+'_collection');
        if (this.inputQuery !== null)
            title += ' - ' + this.inputQuery;
        var url = this.baseUrl;
        if (this.inputQuery !== null)
            url += '?q='+encodeURIComponent(this.inputQuery);
        window.history.replaceState(null, title, url);
        document.title = title;
    },

    showResultsCount: function() {
        this.refs.infoResultsCount.innerText = lang.num(lang('files_search_results_count'), this.resultsCount);
    },

    onSearchInput: function(evt) {
        var q = evt.target.value.trim();
        if (q.length >= this.minQueryLength)
            this.searchThrottled(q);
        else if (q === '')
            this.hideSearch();
    },

    onSearchInputKeyDown: function(evt) {
        if (evt.keyCode === 10 || evt.keyCode === 13) {
            var query = this.refs.input.value.trim();
            if (query === '') {
                this.hideSearch();
            } else {
                this.searchThrottled(query);
            }
        }
    },

    onSearchInputFocus: function(evt) {
        addClass('files_search', 'is-focused');
    },

    onSearchInputBlur: function(evt) {
        removeClass('files_search', 'is-focused');
    },

    onClearIconClick: function(evt) {
        this.refs.input.value = '';
        this.refs.input.focus();
        this.hideSearch();
    },

    onShowMoreClick: function(evt) {
        this.search(this.inputQuery);
    }
});