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 {Bar, BarChart, CartesianGrid, Label, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts';
import SearchAPI from "../../api/SearchAPI";
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 QueryComparisonLineChart!

export default class ComparisonHistogram extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            viewMode: 'absolute', // Sets default view mode to absolute.
            referenceSelection: 'to the collection',  // default is that the collection is the reference
            relData: null,
            absData: this.getJoinedData(this.props.data, 'year') || null,
            searchIds: this.getSearchIds(this.props.data) || null,
            isLoading: false,
            dateUnit: 'year'
        };
        PropTypes.checkPropTypes(ComparisonHistogram.propTypes, this.props, 'prop', this.constructor.name);
    }

    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)

    /*
        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
        });
    };

    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
        })
    };

    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
            }
        );
    };

    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
            }
        );
        }
    };

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

    viewQueryIntervalOnSearchPage = e => {

        if (e.tooltipPayload && e.tooltipPayload[0].dataKey)
        {
            //retrieve query to match selected bar
            let query = JSON.parse(JSON.stringify(this.props.data.find(element => element.searchId == e.tooltipPayload[0].dataKey).query));

            if(query){

            //modify date to match selected bar
            const intervalLimits = TimeUtil.dateToStartAndEndInterval(e.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.removeJSONByKeyInLocalStorage('stored-priority-query');
                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');
            }
            }
        }
    };

    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);
    }

    renderStackBars = dataKeys => dataKeys.map((searchId, index) => (
        <Bar
            key={searchId}
            isAnimationActive={true}
            dataKey={searchId}
            fill={this.props.queryStats[searchId].color}
            stackId="a"
            name=""
            onClick={this.viewQueryIntervalOnSearchPage}
        />)
    );

    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}
            />
    );

    render() {
        const dateOptions = [
        {value: 'day', label: 'day'},
        {value: 'week', label: 'week'},
        {value: 'month', label: 'month'},
        {value: 'year', label: 'year'}]

        const random = Math.floor(Math.random() * 1000) + 1;
        const dataToPrint = this.state.viewMode === 'relative' ? this.state.relData ? this.state.relData : null : this.state.absData;
        const yaxisLabel = this.state.viewMode === 'relative' ? '% compared to ' + this.state.dateUnit : 'Number of records';

        const bars = this.state.searchIds ? this.renderStackBars(this.state.searchIds) : null;
        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-histogram')}>
                <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%">
                    <BarChart
                        key={random}
                        width={1200}
                        height={250}
                        data={dataToPrint}
                        margin={{top: 5, right: 20, bottom: 5, left: 0}}>
                        <CartesianGrid strokeDasharray="3 3"/>
                        <XAxis dataKey="date" height={100}>
                            <Label
                                value= {this.state.dateUnit + " (selected date field varies per query)"}
                                position="outside"
                                offset={0}
                                style={{fontSize: 1.4 + 'rem', fontWeight:'bold'}}
                            />
                        </XAxis>
                        <YAxis width={100} >
                            <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}/>}/>
                        {bars}
                    </BarChart>
                </ResponsiveContainer>
                {dateUnitSelector}
            </div>
        )
    }
}

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