import React from 'react';
import PropTypes from 'prop-types';
import axios from 'axios';
import { connect } from 'react-redux';
import TagListPanel from '../../components/Logbook/Tag/TagListPanel';
import { createTagsByInvestigationId, updateTagsByInvestigationId } from '../../api/icat/icatPlus';
import _ from 'lodash';
import { INFO_MESSAGE_TYPE, ERROR_MESSAGE_TYPE, TAG_MANAGER_CONTEXT, BASIC_EVENT_CONTEXT, NEW_EVENT_CONTEXT, EDIT_EVENT_CONTEXT, FETCHED_STATUS, FETCHING_STATUS, EDIT_TAG_CONTEXT, NEW_TAG_CONTEXT, LOGBOOK_CONTAINER_CONTEXT } from '../../constants/EventTypes';
import NewOrEditTagPanel from '../../components/Logbook/Tag/NewOrEditTagPanel';
import TagListMenu from '../../components/Logbook/Tag/TagListMenu';
import UserMessage from '../../components/UserMessage';
import { SUCCESS_MESSAGE_TYPE } from '../../constants/UserMessages';
import { setAvailableTagAction } from '../../actions/logbook';
import { getTagsByInvestigationId } from './IORequests';
import TagSelector from '../../components/Logbook/Tag/TagSelector';
import TagListInLine from '../../components/Logbook/Tag/TagListInLine';

/**
 * The event tag container in charge of managing event tags.
 */
class TagContainer extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            context: this.props.context,
            message: null,
            selectedTags: [], // contains currently selected tags for a given event when tagContainer is used in the context of an event. Contains the edited tag when used with tag editor.
            tagsUploadStatus: null
        };

        this.isGettingSelectedTagsFromEventProp = true;

        if (props.setTagContainer) {
            props.setTagContainer(this);
        }

        this.addTagToSelection = this.addTagToSelection.bind(this);
        this.createNewTag = this.createNewTag.bind(this);
        this.onEditTagButtonClicked = this.onEditTagButtonClicked.bind(this);
        this.onNewTagButtonClicked = this.onNewTagButtonClicked.bind(this);
        this.removeTagFromSelection = this.removeTagFromSelection.bind(this);
        this.setMessage = this.setMessage.bind(this);
        this.setSelectedTagsFromProps = this.setSelectedTagsFromProps.bind(this);
        this.showAvailableTags = this.showAvailableTags.bind(this);
        this.updateTags = this.updateTags.bind(this);
    }

    render() {

        let userMessage = null;
        if (this.state.message) {
            userMessage = (<UserMessage type={this.state.message.type} message={this.state.message.text} > </UserMessage>);
        }

        if (this.props.context === LOGBOOK_CONTAINER_CONTEXT) {
            // called by the logbook container to get the available tags
            return null;
        }
        if (this.props.availableTagsReceptionStatus === FETCHED_STATUS) {

            if (this.state.context === EDIT_EVENT_CONTEXT || this.state.context === NEW_EVENT_CONTEXT) {
                return (
                    <div style={{ display: 'flex' }}>
                        <div style={{ flex: '0 0 148px' }}>
                            <TagSelector
                                availableTags={this.props.availableTags}
                                onTagCreatedInSelect={this.createNewTag}
                                onTagSelectedInSelect={this.addTagToSelection} />
                        </div>

                        <div style={{ flex: '1 1 100px', overflow: 'hidden' }}>
                            <TagListInLine tags={this.state.selectedTags} hasHorizontalScrolls={true} onDeleteTagClicked={this.removeTagFromSelection} showDeleteButton={true} />
                        </div>
                    </div>);
            }

            // no event is provided. 
            if (this.state.context === TAG_MANAGER_CONTEXT) {
                return (<div>
                    {userMessage}
                    <TagListMenu onNewTagButtonClicked={this.onNewTagButtonClicked} />
                    <TagListPanel
                        availableTags={this.props.availableTags}
                        onEditButtonClicked={this.onEditTagButtonClicked}
                    />
                </div>);
            }

            if (this.state.context === EDIT_TAG_CONTEXT) {
                return (<div>
                    {userMessage}
                    <NewOrEditTagPanel
                        panelHeaderText={'Editing tag'}
                        showAvailableTags={this.showAvailableTags}
                        tag={this.state.selectedTags.length !== 0 ? this.state.selectedTags[0] : null}
                        updateTags={this.updateTags}
                    />
                </div>);
            }

            if (this.state.context === NEW_TAG_CONTEXT) {
                return (<div>
                    {userMessage}
                    <NewOrEditTagPanel
                        createNewTag={this.createNewTag}
                        panelHeaderText={'New tag'}
                        showAvailableTags={this.showAvailableTags}
                        tag={null}
                    />
                </div>);
            }
        };

        if (!this.props.availableTagsReceptionStatus || this.props.availableTagsReceptionStatus === FETCHING_STATUS) {
            return <p> Please wait... </p>;
        };
    }

    componentDidMount() {
        let onSuccess = (data) => {
            if (data.data.tags) {
                this.props.setAvailableTags(data.data.tags); //store available tags in the store
            }
        };

        let onError = (err) => {
            this.setMessage(ERROR_MESSAGE_TYPE, 'Error during tag reception');
        };

        // Checks whether available tags have been fetched already
        if (this.props.availableTagsReceptionStatus === FETCHED_STATUS) {
            if (this.props.context === EDIT_EVENT_CONTEXT || this.props.context === NEW_EVENT_CONTEXT) {
                this.isGettingSelectedTagsFromEventProp = false;
                this.setSelectedTagsFromProps();
            }
        }
        else {
            getTagsByInvestigationId(this.props.user.sessionId, this.props.investigationId, onSuccess, onError); //fetch available tags.
        };
    }

    componentDidUpdate() {
        if (this.props.context === EDIT_EVENT_CONTEXT) {
            if (this.props.availableTagsReceptionStatus === FETCHED_STATUS) {
                if (this.isGettingSelectedTagsFromEventProp === true) {
                    this.isGettingSelectedTagsFromEventProp = false;
                    this.setSelectedTagsFromProps();
                };
            };
        };
    }

    /**
     * Set user selected tags for a given event from props
     */
    setSelectedTagsFromProps() {
        let { event, availableTags } = this.props;

        if (event) {
            let selectedTagsFromEventProp = event.tag.map(tagId => { return _.find(availableTags, (tag) => (tag._id === tagId)); });
            if (!_.isEqual(this.state.selectedTags, selectedTagsFromEventProp)) {
                this.setState({ selectedTags: selectedTagsFromEventProp });
            };
        };
    }

    /** CallBack function triggered when an error occurs. 
     * @param {string} type type of error
     * @param {string} text text of the message
     * @param {string} context the context in which the container should enter (if provided)
    */
    setMessage(type, message, context) {
        if (context) {
            this.setState({ message: { type: type, text: message, context: context } });
        } else {
            this.setState({ message: { type: type, text: message } });
        };
    }

    /**
     * Creates a new tag on the server side
     * @param {Object} newTag tag to create
     */
    createNewTag(newTag) {
        let _this = this;

        let nextContext = (this.props.context === NEW_TAG_CONTEXT) ? TAG_MANAGER_CONTEXT : this.props.context;
        // POST the new tag to the server
        if (newTag) {
            axios({
                method: 'post',
                url: createTagsByInvestigationId(this.props.user.sessionId, this.props.investigationId),
                data: newTag,
            })
                .then((data) => {
                    let newlyCreatedTag = data.data;
                    console.log('The new tag was created successfully on ICAT+ server.');

                    let selectedTags = [];
                    if (this.props.context === EDIT_EVENT_CONTEXT || this.props.context === NEW_EVENT_CONTEXT) {
                        // ADD the new tag in the currently selected tags.
                        selectedTags = _.map(_this.state.selectedTags, _.clone);
                        selectedTags.push(newlyCreatedTag);
                        this.props.canEnableSaveButton({ hasText: true, currentTextEqualsOriginal: false });
                    }

                    // GET the available tags and finally update the component state
                    let onSuccess = (data) => {
                        if (data.data.tags) {
                            this.setState({ context: nextContext, selectedTags: selectedTags });
                            if (this.props.context !== NEW_EVENT_CONTEXT || this.props.context !== EDIT_EVENT_CONTEXT) {
                                this.setMessage(SUCCESS_MESSAGE_TYPE, 'Tag successfully created');
                            }
                            this.props.setAvailableTags(data.data.tags);
                            return;
                        }
                        this.setMessage(ERROR_MESSAGE_TYPE, 'Error in server response', nextContext);
                    };

                    let onError = () => {
                        this.setMessage(ERROR_MESSAGE_TYPE, 'Error while retrieving tags', nextContext);
                    };

                    getTagsByInvestigationId(this.props.user.sessionId, this.props.investigationId, onSuccess, onError);
                })
                .catch((error) => {
                    console.log('[ERROR] the tag could not be created. The error is : ' + error);
                    this.setMessage(ERROR_MESSAGE_TYPE, '[ERROR] Tag not created.', nextContext);
                });
        }
    }

    /**
     * Update tags on the server side
     * @param {Array} tags the tags to update on the server
     */
    updateTags(tags) {
        if (tags) {
            let promises = [];

            tags.forEach(tag => {
                promises.push(
                    axios({
                        method: 'put',
                        url: updateTagsByInvestigationId(this.props.user.sessionId, this.props.investigationId, tag._id),
                        data: tag,
                    })
                );
            });

            Promise.all(promises)
                .then((results) => {
                    let availableTags = _.cloneDeep(this.props.availableTags);
                    results.forEach((data) => {
                        console.log('The tag ' + data.data.name + ' was successfully updated on ICAT+ server.');
                        let tagToBeUpdated = _.find(availableTags, (tag) => { return _.isEqual(tag._id, data.data._id) });
                        _.remove(availableTags, (tag) => { return _.isEqual(tag, tagToBeUpdated) });
                        availableTags.push(data.data);
                    });

                    this.props.setAvailableTags(availableTags);
                    this.setMessage(INFO_MESSAGE_TYPE, 'Tags was successfully updated');
                    this.setState({ context: TAG_MANAGER_CONTEXT });
                })
                .catch((error) => {
                    console.log('[ERROR] At least one tag could not be updated. The error is : ' + error);
                    this.setMessage(ERROR_MESSAGE_TYPE, '[ERROR] All tags could not be updated.');
                });
        };
    }

    /** Add a tag to the selected Tags */
    addTagToSelection(tag) {
        let selectedTags = _.map(this.state.selectedTags, _.clone);
        selectedTags.push(tag);
        // enable the save button in detailed event view
        this.props.canEnableSaveButton({ hasText: true, currentTextEqualsOriginal: false });

        this.setState({
            message: null,
            selectedTags: selectedTags
        });
    }

    /**
    * Remove the given tag from the current tags
    * @param {*} tag 
    */
    removeTagFromSelection(tag) {
        if (tag) {
            // enable the save button in detailed event view
            this.props.canEnableSaveButton({ hasText: true, currentTextEqualsOriginal: false });

            this.setState({
                message: null,
                selectedTags: _.filter(this.state.selectedTags, (value) => (_.isEqual(value, tag) === false)),
            });
        };
    }

    /**
     * Edit a given tag
     * @param {*} tag the tag to be edited
     */
    onEditTagButtonClicked(tag) {
        if (tag) {
            this.setState({
                context: EDIT_TAG_CONTEXT,
                message: null,
                selectedTags: [tag],
            });
        };
    }

    onNewTagButtonClicked() {
        this.setState({
            context: NEW_TAG_CONTEXT,
            message: null,
        });
    }
    /**
     * Shows available tags in the tag manager
     */
    showAvailableTags() {
        this.setState({
            context: TAG_MANAGER_CONTEXT,
        });
    }
}

/**
 * Redux related function which is used to map local props with redux state
 * @param {*} state redux state
 */
function mapStateToProps(state) {
    return {
        availableTags: state.logbook.availableTags,
        availableTagsReceptionStatus: state.logbook.availableTagsReceptionStatus,
        user: state.user
    };
}

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

TagContainer.propTypes = {
    /* the event to display the tags of. No events means we use the TagManager */
    event: PropTypes.object,
    /* the investigationId to display the event from */
    investigationId: PropTypes.string.isRequired,
    /* Callback function used to enable the save button for a detailed event */
    canEnableSaveButton: PropTypes.func,
    setTagContainer: PropTypes.func,
};

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