import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { getEventsByInvestigationId, getEventCountByInvestigationId, createEvent, updateEvent, getTagsByInvestigationId } from './IORequests';
import _ from 'lodash';

import EventList from '../../components/Logbook/List/EventList';
import EventListMenu from '../../components/Logbook/Menu/EventListMenu.js';
import { NEW_EVENT_INVISIBLE, NEW_EVENT_VISIBLE, LIST_VIEW, ERROR_MESSAGE_TYPE, EDIT_EVENT_CONTEXT, EDIT_EVENT_VISIBLE, EDIT_EVENT_INVISIBLE, NEW_EVENT_CONTEXT, LOGBOOK_CONTAINER_CONTEXT } from '../../constants/EventTypes.js';
import EventPager from '../../components/Logbook/EventPager.js';
import { convertPageToPageEventIndexes, isRangeAvailableOnTheClient } from '../../helpers/PaginationHelper.js';
import { GUI_CONFIG } from '../../config/gui.config.js';
import Loading from '../../components/Loading.js';
import UserMessage from '../../components/UserMessage.js';
import { getSelectionFiltersBySearchCriteria, getSelectionFiltersForMongoQuery, getSelectionFilterForAllAnnotationsAndNotifications } from './SelectionFilterHelper.js';
import { clearAvailableTagAction } from '../../actions/logbook.js';
import OverlayBox from '../../components/Logbook/OverlayBox.js';
import NewOrEditEventPanel from '../../components/Logbook/NewOrEditEventPanel.js';
import "../../components/Logbook/event.css"
import { INFO_MESSAGE_TYPE } from '../../constants/UserMessages.js';
import PeriodicRefresher from './PeriodicRefresher.js';
import TagContainer from './TagContainer';

/**
 * This class represents the event container component. It's role is to retrieve events from the server asynchronuously. 
 * Also, it is connected to redux and retrieves user data and sessionId.
 */
export class LogbookContainer extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            activePage: 1,
            eventCountSinceLastRefreshForCurrentSelectionFilter: 0,  // count of events for the current selection filter found in the logbook since last refresh
            eventCountSinceLastRefresh: 0, // count of all events found in the logbook since last refresh
            downloadedEvents: [], //events already downloaded
            errorMessage: '',
            fetchingEventList: true,
            fetchedEventList: false,
            fetchingEventCount: true,
            fetchedEventCount: false,

            eventBeingEdited: null,
            isNewEventVisible: false,
            isEventListAutorefreshEnabled: GUI_CONFIG().AUTOREFRESH_EVENTLIST,
            pageEvents: [], // events currently displayed on the page
            selectedEventId: 0,
            sortingFilter: GUI_CONFIG().DEFAULT_SORTING_FILTER,
            userSearchCriteria: [],
            view: LIST_VIEW,
        };

        this.getEvents = this.getEvents.bind(this);
        this.isAppProperlyConfigured = this.isAppProperlyConfigured.bind(this);
        this.onEventsReceptionFailure = this.onEventsReceptionFailure.bind(this);
        this.onEventCountReceptionFailure = this.onEventCountReceptionFailure.bind(this);
        this.onEventClicked = this.onEventClicked.bind(this);
        this.onEventUpdated = this.onEventUpdated.bind(this);
        this.onNewEventsReceived = this.onNewEventsReceived.bind(this);
        this.onNewEventUploaded = this.onNewEventUploaded.bind(this);
        this.onPageClicked = this.onPageClicked.bind(this);
        this.reverseEventsSortingByCreationDate = this.reverseEventsSortingByCreationDate.bind(this);
        this.searchEvents = this.searchEvents.bind(this);
        this.setEditEventVisibility = this.setEditEventVisibility.bind(this);
        this.setEventListAutorefresh = this.setEventListAutorefresh.bind(this);
        this.setNewEventVisibility = this.setNewEventVisibility.bind(this);
        this.setView = this.setView.bind(this);
    }

    render() {
        const { investigationId, user } = this.props;
        const selectionFilter = getSelectionFiltersForMongoQuery(this.state.userSearchCriteria, this.state.sortingFilter, this.state.view);

        if (this.isAppProperlyConfigured()) {
            return (
                <div style={{ marginBottom: '40px' }}>

                    <TagContainer context={LOGBOOK_CONTAINER_CONTEXT} investigationId={investigationId} />

                    <OverlayBox open={this.state.errorMessage ? true : false} onClose={() => { this.setState({ errorMessage: null }) }} classNames={{ modal: 'userMessageModalClass', closeButton: 'userMessageCloseButton' }}>
                        <UserMessage type={ERROR_MESSAGE_TYPE} message={this.state.errorMessage} />
                    </OverlayBox>

                    <PeriodicRefresher
                        initialValue={this.state.pageEvents.length}
                        intervalInMilliSeconds={GUI_CONFIG().AUTOREFRESH_DELAY}
                        isEnabled={GUI_CONFIG().AUTOREFRESH_ENABLED}
                        onCallbackSuccess={this.onNewEventsReceived}
                        periodicCallback={(onSuccess, onFailure) => getEventsByInvestigationId(user.sessionId, investigationId, GUI_CONFIG().EVENTS_PER_DOWNLOAD, 0, selectionFilter.find, GUI_CONFIG().DEFAULT_SORTING_FILTER, onSuccess, onFailure)}>
                        <EventListMenu
                            availableTags={this.props.availableTags}
                            eventCountSinceLastRefresh={this.state.eventCountSinceLastRefresh}
                            getEvents={this.getEvents}
                            investigationId={investigationId}
                            isNewEventVisible={this.state.isNewEventVisible}
                            isEventListAutorefreshEnabled={this.state.isEventListAutorefreshEnabled}
                            numberOfMatchingEventsFound={this.state.eventCountSinceLastRefreshForCurrentSelectionFilter}
                            reverseEventsSortingByCreationDate={this.reverseEventsSortingByCreationDate}
                            searchEvents={this.searchEvents}
                            sessionId={user.sessionId}
                            setEventListAutorefresh={this.setEventListAutorefresh}
                            setNewEventVisibility={this.setNewEventVisibility}
                            setView={this.setView}
                            sortingFilter={this.state.sortingFilter}
                            selectionFilter={selectionFilter}
                            view={this.state.view}
                        />
                    </PeriodicRefresher>

                    <OverlayBox open={this.state.isNewEventVisible === true || (this.state.eventBeingEdited ? true : false)} classNames={{ overlay: 'newOrEditOverlayClass', modal: 'newOrEditModalClass' }} onClose={() => null}>
                        <div style={{ height: this.getPanelHeightInPercent(NEW_EVENT_CONTEXT), paddingBottom: this.state.eventBeingEdited ? '5px' : '0px' }}>
                            {this.state.isNewEventVisible === true ?
                                <NewOrEditEventPanel
                                    context={NEW_EVENT_CONTEXT}
                                    createEvent={createEvent}
                                    investigationId={investigationId}
                                    onNewEventUploaded={this.onNewEventUploaded}
                                    setNewEventVisibility={this.setNewEventVisibility}
                                    user={user}
                                />
                                : null}
                        </div>

                        <div style={{ height: this.getPanelHeightInPercent(EDIT_EVENT_CONTEXT), paddingBottom: this.state.isNewEventVisible === true ? '5px' : '0px' }}>
                            {this.state.eventBeingEdited ?
                                <NewOrEditEventPanel
                                    context={EDIT_EVENT_CONTEXT}
                                    event={this.state.eventBeingEdited}
                                    investigationId={investigationId}
                                    onEventUpdated={this.onEventUpdated}
                                    setEditEventVisibility={this.setEditEventVisibility}
                                    user={user}
                                    updateEvent={updateEvent} />
                                : null}
                        </div>
                    </OverlayBox>

                    {this.state.fetchedEventList === false && this.state.fetchingEventList === true ? <Loading message='Loading logbook'></Loading> : null}

                    {this.state.fetchedEventList === true ?
                        <div>
                            <EventPager
                                activePage={this.state.activePage}
                                eventCount={this.state.eventCountSinceLastRefreshForCurrentSelectionFilter}
                                isFloatingRight={true}
                                onPageClicked={this.onPageClicked} />

                            {(this.state.pageEvents.length === 0) ?
                                <UserMessage message='The logbook is empty.' type={INFO_MESSAGE_TYPE} isTextCentered={true} />
                                : <EventList
                                    availableTags={this.props.availableTags}
                                    events={this.state.pageEvents}
                                    onEventClicked={this.onEventClicked} />
                            }

                            <EventPager
                                activePage={this.state.activePage}
                                eventCount={this.state.eventCountSinceLastRefreshForCurrentSelectionFilter}
                                onPageClicked={this.onPageClicked} />
                        </div> : null}
                </div >
            );
        } else {
            return (<div style={{ marginBottom: '40px' }}>
                <UserMessage type={ERROR_MESSAGE_TYPE} message={'Sorry, the application is not properly configured for wall-eLog. Can not start. Check the console.'} />
            </div>
            );
        }
    }

    /**
    * Executed the first time the component is mounted
    */
    componentWillMount() {
        this.getEvents(0, getSelectionFiltersBySearchCriteria(this.state.userSearchCriteria, this.state.view), true);
    }

    componentWillUnmount() {
        this.props.clearAvailableTags();
    }

    /** Get the height percentage value for the panels for new and edition of an event */
    getPanelHeightInPercent(context) {
        if (context) {
            if (context === NEW_EVENT_CONTEXT) {
                return this.state.isNewEventVisible === false ? '0%' : this.state.eventBeingEdited === null ? '100%' : '50%';
            }
            if (context === EDIT_EVENT_CONTEXT) {
                return this.state.eventBeingEdited === null ? '0%' : this.state.isNewEventVisible === true ? '50%' : '100%';
            }
        }
        return '100%';
    }

    /**
     * Callback function which refreshes the event list because the events the user want to see have not been downloaded yet
     * @param {integer} offset the offset
     * @param {object} sortingFilter the sorting filter to use , optional
     * @param {Object} selectionFilter optional; selection filter in mongodb format
     * @param {bool} isEventCountRequestNeeded whether the event count request is needed or not
     */
    getEvents(offset, selectionFilter, isEventCountRequestNeeded, sortingFilter) {
        const { investigationId, user } = this.props;
        sortingFilter = sortingFilter || GUI_CONFIG().DEFAULT_SORTING_FILTER;
        if (!selectionFilter) {
            // there is not selectionfilter as parameter when the user has clicked on a page
            selectionFilter = getSelectionFiltersBySearchCriteria(this.state.userSearchCriteria, this.state.view);
        };

        //Get all events
        this.setState({ isNewEventVisible: false });

        const onSuccess = (data) => {
            this.setState({
                downloadedEvents: data.data,
                downloadedEventIndexes: {
                    start: offset,
                    end: offset + data.data.length - 1
                },
                fetchedEventList: true,
                pageEvents: _.slice(data.data, 0, GUI_CONFIG().EVENTS_PER_PAGE),
                selectionFilter: selectionFilter,
                sortingFilter: sortingFilter,
            });
        }

        getEventsByInvestigationId(user.sessionId, investigationId, GUI_CONFIG().EVENTS_PER_DOWNLOAD, offset, selectionFilter, sortingFilter, onSuccess, this.onEventsReceptionFailure);

        // then possibly get event count for the current active selection filter
        if (isEventCountRequestNeeded) {
            this.setState({ fetchingEventCount: true });
            const onSuccess = (data) => {
                this.setState({
                    eventCountSinceLastRefreshForCurrentSelectionFilter: parseInt(data.data, 10),
                    fetchedEventCount: true
                });
            }
            getEventCountByInvestigationId(investigationId, user.sessionId, selectionFilter, onSuccess, () => this.onEventCountReceptionFailure('eventCountSinceLastRefreshForCurrentSelectionFilter'));
        }

        // Then get count of all events in the logbook used for the autorefresh feature
        const onSuccess2 = (data) => {
            this.setState({ eventCountSinceLastRefresh: parseInt(data.data, 10) });
        }
        getEventCountByInvestigationId(investigationId, user.sessionId, getSelectionFilterForAllAnnotationsAndNotifications(), onSuccess2, () => this.onEventCountReceptionFailure('eventCountSinceLastRefresh'));
    }

    /**
     * Callback function triggered when event data retreval failed
     * @param {*} error
     */
    onEventsReceptionFailure(error) {
        console.log('An error occured while retrieving the logs' + error);
        this.setState({
            errorMessage: 'An error occured while retrieving the logs',
            events: [],
            fetchingEventList: false,
        });
    }

    /**
     * Callback function triggered when event count retreval failed
     * @param {*} error
     */
    onEventCountReceptionFailure(eventCounterToReset) {
        console.log('An error occured while retrieving the count of logs');
        this.setState({
            errorMessage: 'An error occured while retrieving the count of logs',
            [eventCounterToReset]: 0,
            fetchingEventCount: false,
        });
    }

    onEventClicked(event) {
        if (this.state.eventBeingEdited && this.state.eventBeingEdited != event) {
            this.setState({ errorMessage: 'Action refused. Please save or cancel the log which is currently being edited.' });
        }
        this.setState({ eventBeingEdited: event });
    }

    /** Callback function triggered when an event as been updated.
     *  @param {Object} event the updated event as provided by the server
     */
    onEventUpdated(updatedEvent) {
        let copyPageEvents = this.state.pageEvents.map(event => event);
        let foundIndex1 = _.findIndex(copyPageEvents, (event) => event._id === updatedEvent._id);
        copyPageEvents.splice(foundIndex1, 1, updatedEvent);

        let copyDownloadedEvents = this.state.downloadedEvents.map(event => event);
        let foundIndex2 = _.findIndex(copyDownloadedEvents, (event) => event._id === updatedEvent._id);
        copyDownloadedEvents.splice(foundIndex2, 1, updatedEvent);

        this.setState({
            downloadedEvents: copyDownloadedEvents,
            pageEvents: copyPageEvents,
            eventBeingEdited: null
        });
    }

    /**
     * Callback triggered periodically by the periodicRefresher
     */
    onNewEventsReceived() {
        if (this.state.isEventListAutorefreshEnabled === true) {
            this.getEvents(0, getSelectionFiltersBySearchCriteria(this.state.userSearchCriteria, this.state.view), true);
        }
    }

    /**
     * Callback function triggered when a new event has been successfully uploaded
     */
    onNewEventUploaded() {
        this.setState({
            isNewEventVisible: false,
            activePage: 1,
        });

        this.getEvents(0, getSelectionFiltersBySearchCriteria(this.state.userSearchCriteria, this.state.view), true);
    }

    /**
     * Callback function triggered when the user clicks on a page link
     * @param {integer} page the page the user has clicked
     */
    onPageClicked(page) {
        if (page) {
            let pageEventIndexes = convertPageToPageEventIndexes(page, GUI_CONFIG().EVENTS_PER_PAGE, this.state.eventCountSinceLastRefreshForCurrentSelectionFilter);
            let areEventsAlreadyDownloaded = isRangeAvailableOnTheClient(pageEventIndexes, this.state.downloadedEventIndexes);

            if (areEventsAlreadyDownloaded) {
                this.setState({
                    pageEvents: _.slice(this.state.downloadedEvents,
                        pageEventIndexes.start - this.state.downloadedEventIndexes.start,
                        pageEventIndexes.end - this.state.downloadedEventIndexes.start + 1)
                });
            } else {
                this.getEvents(pageEventIndexes.start, false, false, this.state.sortingFilter);
            }

            this.setState({
                activePage: page,
                selectedEventId: 0
            });
        }
    }

    /** Callback function triggered when the user starts a new search
     * @param {Array} searchCriteria the search criteria as provided by the search component
     */
    searchEvents(searchCriteria) {
        this.getEvents(0, getSelectionFiltersBySearchCriteria(searchCriteria, this.state.view), true);
        this.setState({ userSearchCriteria: searchCriteria });
    }

    /**
     * Set the visibility of the edit event panel
     * @param {string} newVisibility the requested visibility
     */
    setEditEventVisibility(newVisibility) {
        if (newVisibility) {
            if (newVisibility === EDIT_EVENT_VISIBLE) {
                //this.setState({ isNewEventVisible: true });
            } else if (newVisibility === EDIT_EVENT_INVISIBLE) {
                this.setState({ eventBeingEdited: null });
            }
        };
    }

    /**
     * Set the current event list auto refresh mode
     * @param {bool} value new value for the mode
     */
    setEventListAutorefresh(value) {
        let newChoice = value === undefined ? false : value;
        if (this.state.isEventListAutorefreshEnabled !== value) {
            this.setState({ isEventListAutorefreshEnabled: value })
        }
    }

    /**
     * Set the visibility of the new event panel
     * @param {string} visibility the requested visibility
     */
    setNewEventVisibility(visibility) {
        if (visibility) {
            if (visibility === NEW_EVENT_VISIBLE) {
                this.setState({ isNewEventVisible: true });
            } else if (visibility === NEW_EVENT_INVISIBLE) {
                this.setState({ isNewEventVisible: false });
            }
        };
    }

    /**
     * Change the current view to the requested view
     * @param {string} view the requested view
     */
    setView(view) {
        if (view && view !== this.state.view) {
            this.getEvents(0, getSelectionFiltersBySearchCriteria(this.state.userSearchCriteria, view), true);
        }
        this.setState({ view: view });
    }

    /**
     * Callback function which reverses the sorting of events by creation date
     * @param {string} selectedEventId the id of the selected event
     */
    reverseEventsSortingByCreationDate(selectedEventId) {
        let newSortingFilter = {};
        let selectionFilter = getSelectionFiltersBySearchCriteria(this.state.userSearchCriteria, this.state.view);
        /* temporary fix , in the future we will use creationDate instead of createdAt*/
        newSortingFilter.createdAt = this.state.sortingFilter.createdAt * (-1);

        if (selectedEventId !== undefined) {
            // An event is selected, sort and show the page where the selected event is
            let indexOfEventOnThePage = _.findIndex(this.state.pageEvents, (event) => { return event._id === selectedEventId; });

            if (indexOfEventOnThePage !== -1) {
                let indexOfEventOnAllEventsFound = indexOfEventOnThePage + (this.state.activePage - 1) * GUI_CONFIG().EVENTS_PER_PAGE;
                let rankInFutureList = this.state.eventCountSinceLastRefreshForCurrentSelectionFilter - indexOfEventOnAllEventsFound;
                let newPage = Math.ceil(rankInFutureList / GUI_CONFIG().EVENTS_PER_PAGE);
                let offset = (newPage - 1) * GUI_CONFIG().EVENTS_PER_PAGE;
                this.getEvents(offset, selectionFilter, false, newSortingFilter);
                this.setState({
                    activePage: newPage,
                    selectedEventId: selectedEventId
                });
                return;
            }
        }
        this.getEvents(0, selectionFilter, false, newSortingFilter);
        this.setState({ activePage: 1, selectedEventId: 0 });
    }

    /**
     * Checks whether the app is properly configured for wall-eLog
     * @returns {boolean} true when the app is properly configured
     */
    isAppProperlyConfigured() {
        if (!GUI_CONFIG()) {
            console.log('[ERROR] [CONFIG] File gui.config.js is missing');
            return false;
        }
        if (!GUI_CONFIG().DEFAULT_SORTING_FILTER) {
            console.log('[ERROR] [CONFIG] DEFAULT_SORTING_FILTER object is missing');
            return false;
        }
        /* temporary workaround. We will need to use creationDate in fact in the future */
        if (!GUI_CONFIG().DEFAULT_SORTING_FILTER.createdAt) {
            console.log('[ERROR] [CONFIG] DEFAULT_SORTING_FILTER.creationDate value is missing');
            return false;
        }
        if (!GUI_CONFIG().EVENTS_PER_PAGE) {
            console.log('[ERROR] [CONFIG] EVENTS_PER_PAGE value is missing');
            return false;
        }
        return true;
    }
}

function mapStateToProps(state) {
    return {
        user: state.user,
        availableTags: state.logbook.availableTags
    };
}

/**
 * Redux related function which is used to map local function with redux dispatch()
 * @param {*} dispatch redux dispatch function
 */
function mapDispatchToProps(dispatch) {
    return {
        clearAvailableTags: () => dispatch(clearAvailableTagAction())
    };
}

LogbookContainer.propTypes = {
    /** the investigationId */
    investigationId: PropTypes.string.isRequired
};

export default connect(mapStateToProps, mapDispatchToProps)(LogbookContainer);