import moment from 'moment';
import React from 'react';
import PropTypes from 'prop-types';
import Select from 'react-select';

import IDUtil from '../../util/IDUtil';
import TimeUtil from '../../util/TimeUtil';
import ComponentUtil from '../../util/ComponentUtil';
import {LineChart, Label, Line, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContainer} from 'recharts';
import SearchAPI from '../../api/SearchAPI';
import ElasticsearchDataUtil from "../../util/ElasticsearchDataUtil";
import CustomTooltip from './helpers/CustomTooltip';
import Loading from '../../components/shared/Loading';
import FlexRouter from '../../util/FlexRouter';
import LocalStorageHandler from '../../util/LocalStorageHandler';

//implemented using recharts: http://recharts.org/en-US/examples

//FIXME part of this code is duplicate with whatever is in the ComparisonHistogram!

export default class QueryComparisonLineChart extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            viewMode: this.props.data.total ? 'inspector' : 'absolute',
            referenceSelection: 'to the collection',  // default is that the collection is the reference
            absData: this.getJoinedData(this.props.data, 'year') || null,
            searchIds: this.getSearchIds(this.props.data) || null,
            relData : null,
            isLoading: false,
            dateUnit: 'year'
        };

        PropTypes.checkPropTypes(QueryComparisonLineChart.propTypes, this.props, 'prop', this.constructor.name);
    }

    shouldComponentUpdate(nextProps, nextState) {
        return (
            nextProps.data !== this.props.data
            || nextState.viewMode !== this.state.viewMode
            || this.state.isLoading !== nextState.isLoading
        );
    }

    onQueryDataReceived = (queryData) => { //array containing instances of SearchResults
    const absValues = this.getJoinedData(queryData, this.state.dateUnit);

        if(this.state.viewMode === 'relative')
        {
            this.state.absData = absValues;
            this.processRelativeData(this.props.data);
        }
        else
        {
                this.setState({
                viewMode: 'absolute',
                absData: absValues,
                isLoading: false
            }
        );
        }
    };

    onRelativeDataReceived = (relativeData) => { //array containing instances of SearchResults

        const relativeValues = this.getRelValues(this.state.absData, relativeData, this.state.dateUnit);

        this.setState({
                viewMode: 'relative',
                relData: relativeValues,
                isLoading: false
            }
        );
    };

    getRelValues = (absoluteValues, relativeValues, dateUnit) => {

        const relVals = this.__getGraphData(relativeValues, 'obj', dateUnit);

        return absoluteValues.map(point => {
            const relPoint = {date: point["date"], "date_millis": point["date_millis"]};
            Object.keys(point).forEach(prop => {
                if (prop !== 'date' && prop !== 'key' && prop !== 'date_millis' && prop !== 'key_as_string' && prop !== 'doc_count') {
                    const tt = relVals[prop].find(obj => obj.date === point.date);
                    relPoint[prop] = (tt[prop] === 0 || point[prop] === 0
                        ? 0
                        : point[prop] / tt[prop] * 100
                    );
                }
            });
            return relPoint
        })
    };


    async getQueryData(key) {
        const chartData = this.getChartDataByKey(key);

        let query = null;
        this.setDateUnit(chartData.query, this.state.dateUnit);

    //get the values for each query
        query = {
            ...chartData.query
        };
        //FIXME this is not right, fix this with the query model
        return new Promise(function(resolve, reject) {
            SearchAPI.search(
                query,
                chartData.collectionConfig,
                data => resolve(data),
                false
            )
        })
    }

    async getReferenceData(key) {
        const chartData = this.getChartDataByKey(key);

        let query = null;
        this.setDateUnit(chartData.query, this.state.dateUnit);
        if (this.state.referenceSelection === 'to the collection')
        {
        //get the values for the whole collection as reference
            query = {
                ...chartData.query,
                term: '',
                selectedFacets: {},
                fieldCategory: []
            };
        }
        else
        {
        //get the values for the query without search term as reference
            query = {
                    ...chartData.query,
                    term: ''
                };
        }

        //FIXME this is not right, fix this with the query model
        return new Promise(function(resolve, reject) {
            SearchAPI.search(
                query,
                chartData.collectionConfig,
                data => resolve(data),
                false
            )
        })
    }

    setDateUnit = (query, dateUnit) => {
        //set the desired date unit in the date histogram facets
        const df = query.desiredFacets;
        df.filter(facet => facet.type === 'date_histogram').forEach(function(facet, index, df){df[index].dateUnit = dateUnit})
        }

    getSearchIds = dataSets => dataSets.map(item => item.query.searchId)

    async processRelativeData(data) {
        const promises = Object.keys(data).map(this.getReferenceData.bind(this));
        await Promise.all(promises).catch(d => console.log(d)).then(
            dataPerQuery => this.onRelativeDataReceived(dataPerQuery)
        );
    }

    async processQueryData(data, dateUnit) {
        const promises = Object.keys(data).map(this.getQueryData.bind(this));
        await Promise.all(promises).catch(d => console.log(d)).then(
            dataPerQuery => this.onQueryDataReceived(dataPerQuery)
        );
    }

    switchViewMode = () => {
        //need new data if viewmode is absolute (means new viewmode will be relative) and EITHER:
        //- there is no relative data
        // OR
        // - the relative data is not relative to the collection (when switching mode to relative the default is 'to the collection')
        const newDataNeeded = this.state.viewMode === 'absolute' && (this.state.relData === null ||(this.state.referenceSelection!= "to the collection"))
        this.setState({
            isLoading: newDataNeeded,
            viewMode: this.state.viewMode === 'relative' ? 'absolute' : 'relative'
        }, () => {
            if(newDataNeeded) {
                this.state.referenceSelection = "to the collection"  //switching from absolute to relative so set to the default ref
                this.processRelativeData(this.props.data, this.state.dateUnit)
            }
        });
    };

    switchReference = (e) => {
        this.state.referenceSelection = e.target.checked ? 'to the query facets': 'to the collection';
        this.setState({isLoading: true})
        this.processRelativeData(this.props.data, this.state.dateUnit);
    }

    onDateUnitChange = (e) => {
        this.setState({isLoading: true})
        this.state.dateUnit = e.value;
        this.processQueryData(this.props.data, this.state.dateUnit);
    }

        /*
        Gets an object with query info with queryId as key.
        Every object has the data as an array {queryId: 'xxx', <dateUnit>: 'xxxx'}
    */
    __getGraphData = (searchResultsList, returnedType, dateUnit) => {
        let dataArr = [];
        let dataObj = {};
        searchResultsList.forEach(searchResults => {
            const dateBuckets = searchResults.aggregations[searchResults.query.dateRange.field].filter(
                dateInfo => dateInfo && dateInfo.hasOwnProperty('date_millis') // make sure this exists
            ).map(dateInfo => { //add two fields
                const extraInfo = {date : TimeUtil.dateToInterval(dateInfo.date_millis, dateUnit)}
                extraInfo[searchResults.searchId] = dateInfo.doc_count
                return Object.assign(dateInfo, extraInfo)
            });
           if(returnedType === 'arr') {
               dataArr.push(dateBuckets)
           } else {
               dataObj[searchResults.searchId] = dateBuckets
           }
        });
        return returnedType === 'arr' ? dataArr : dataObj;
    };

    getJoinedData = (dataSet, dateUnit) => {

        const relGraphData = this.__getGraphData(dataSet, 'arr', dateUnit); //Note: this is a list of arrays!

       //first get a range for the dates in the datasets
        const __calcDate= (queryResultList, mathFunc) => {
            // returns an array with 1 array per query with the years only
            let calcDates = queryResultList.map(dateList => {
                const milliList = dateList.map(d => d.date_millis);
                return mathFunc(...milliList);
            });
            return mathFunc(...calcDates);
        };

        const minDate = moment(new Date(__calcDate(relGraphData, Math.min)));
        const maxDate = moment(new Date(__calcDate(relGraphData, Math.max)));

        let numberOfIntervals = maxDate.diff(minDate, dateUnit + 's') + 1;

        //generates an array of dates based on the min & max dates
        const __validRange = Array.from(new Array(numberOfIntervals), (val,index) => {let newDate = minDate.clone(); newDate.add(index, dateUnit); return newDate});

       //now organise the data for each of these dates
        return __validRange.map(date => {
            let tempObj = {};
            let dateInterval = TimeUtil.dateToInterval(date.toDate(), dateUnit);
            relGraphData.forEach(set => {
                const matchData = set.find(it => it.date === dateInterval);
                if (matchData !== undefined) {
                    tempObj = {...tempObj, ...matchData};
                } else {
                    tempObj = {...tempObj, ...{'date': dateInterval}};
                }
            });
            return tempObj
        });
    };

    getChartDataByKey = (key) => {
        return this.props.data[key];
    }

    viewQueryIntervalOnSearchPage = (e, searchId, clickedIndex) => {

        if (searchId)
        {
            //retrieve query to match selected bar
            let query = JSON.parse(JSON.stringify(this.props.data.find(element => element.searchId == searchId).query));
            let date_millis = this.state.absData[clickedIndex].date_millis;
            if(query && date_millis){

            //modify date to match selected bar
            const intervalLimits = TimeUtil.dateToStartAndEndInterval(date_millis, this.state.dateUnit, "YYYY-MM-DD");

            if(intervalLimits.length === 2){
                query.searchId = null;
                query.dateRange.start = intervalLimits[0];
                query.dateRange.end = intervalLimits[1];

                //store the query in the cache
                LocalStorageHandler.storeJSONInLocalStorage(
                    'stored-search-results',
                    {query: query} //only the query, the Search page will fill the results
                );
                // hide the Search histogram as the minimum interval is 1 year, so it is meaningless and takes up space
                LocalStorageHandler.storeJSONInLocalStorage(
                    'state-show-timeline',
                    false
                );

                //open the Search page
                FlexRouter.gotoSingleSearch('cache');
            }
            }
        }
    }

    renderLines = dataKeys => dataKeys.map((searchId, index) => (
                <Line
                    key={searchId}
                    isAnimationActive={true}
                    name=""
                    label={<LabelAsPoint key={searchId} onClick={this.viewQueryIntervalOnSearchPage}/>} //the LabelAsPoint class handles the onclick of a dot
                    type="monotone"
                    dataKey={searchId}
                    stroke={this.props.queryStats[searchId].color}
                    dot={{stroke: this.props.queryStats[searchId].color, strokeWidth: 2}}
                    activeDot = {false}
                />));

    renderReferenceSelector = () => (
        <div className="ms_toggle_btn ms_toggle_btn_2" >
            <input id="toggle-2" className="checkbox-toggle checkbox-toggle-round" type="checkbox" onClick={this.switchReference}/>
            <label htmlFor="toggle-2" data-on="to the query facets" data-off="to the collection"/>
        </div>
    );

    renderDateUnitSelector = (selectedDateUnit, dateOptions) => (
        <Select
                className="basic-single date_unit_selector"
                classNamePrefix="select"
                defaultValue={selectedDateUnit ? dateOptions.find(option => option.value == selectedDateUnit) : dateOptions[3]}
                name="date-unit"
                options={dateOptions}
                onChange={this.onDateUnitChange}
            />
    );

    //TODO better ID!! (include some unique part based on the query)
    render() {
         const dateOptions = [
        {value: 'day', label: 'day'},
        {value: 'week', label: 'week'},
        {value: 'month', label: 'month'},
        {value: 'year', label: 'year'}]

        const lines = this.renderLines(this.state.searchIds);
        const timelineData = this.state.viewMode === 'relative' ? this.state.relData : this.state.absData;
        const yaxisLabel = this.state.viewMode === 'relative' ? '% compared to ' + this.state.dateUnit : 'Number of records';
        const loadingMsg = this.state.isLoading ? <Loading message="Loading graph..."/> : null;
        const referenceSelector = this.state.viewMode === 'relative' ? this.renderReferenceSelector() : null;
        const dateUnitSelector = this.renderDateUnitSelector(this.state.dateUnit, dateOptions);

        return (
            <div className={IDUtil.cssClassName('query-comparison-line-chart')}>
                <div className="ms_toggle_btns">
                    <div className="ms_toggle_btn ms_toggle_btn_1" >
                        <input id="toggle-1" className="checkbox-toggle checkbox-toggle-round" type="checkbox" onClick={this.switchViewMode}/>
                        <label htmlFor="toggle-1" data-on="Relative" data-off="Absolute"/>
                    </div>
                    {referenceSelector}
                </div>
                {loadingMsg}
                <ResponsiveContainer width="100%" minHeight="400px" height="40%">
                    <LineChart
                        width={1200}
                        height={250}
                        data={timelineData}
                        margin={{top: 5, right: 20, bottom: 5, left: 0}}>
                        <CartesianGrid stroke="#cacaca"/>
                        <XAxis dataKey="date" height={100} tickSize={10}>
                            <Label
                                value= {this.state.dateUnit + " (selected date field varies per query)"}
                                position="outside"
                                offset={100}
                                style={{fontSize: 1.4 + 'rem', fontWeight:'bold'}}
                            />
                        </XAxis>
                        <YAxis width={100} tickFormatter={ComponentUtil.formatNumber}>
                            <Label
                                value={yaxisLabel}
                                offset={30}
                                position="insideBottomLeft"
                                angle={-90}
                                style={{fontSize: 1.4 + 'rem', fontWeight:'bold', height: 460 + 'px', width: 100 + 'px' }}
                            />
                        </YAxis>
                        <Tooltip content={
                            <CustomTooltip
                                active={false}
                                payload={[]}
                                queryStats={this.props.queryStats}
                                viewMode={this.state.viewMode}
                            />
                        }/>
                        {lines}
                    </LineChart>
                </ResponsiveContainer>
                {dateUnitSelector}
            </div>
        )
    }
}

QueryComparisonLineChart.propTypes = {
    data: PropTypes.array.isRequired,
    queryStats: PropTypes.object
};

class LabelAsPoint extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        const {x, y} = this.props;
        return (
            <circle
                className="dot"
                onClick={(e) => {
      this.props.onClick(e, this.props.content.key, this.props.index);
   }}
                cx={x}
                cy={y}
                r={4}
                fill="transparent"/>
        );
    }
}
