/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import * as echarts from '../../echarts';
import * as zrUtil from 'zrender/src/core/util';
import * as graphic from '../../util/graphic';
import * as formatUtil from '../../util/format';
import * as numberUtil from '../../util/number';

var MONTH_TEXT = {
    EN: [
        'Jan', 'Feb', 'Mar',
        'Apr', 'May', 'Jun',
        'Jul', 'Aug', 'Sep',
        'Oct', 'Nov', 'Dec'
    ],
    CN: [
        '一月', '二月', '三月',
        '四月', '五月', '六月',
        '七月', '八月', '九月',
        '十月', '十一月', '十二月'
    ]
};

var WEEK_TEXT = {
    EN: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
    CN: ['日', '一', '二', '三', '四', '五', '六']
};

export default echarts.extendComponentView({

    type: 'calendar',

    /**
     * top/left line points
     *  @private
     */
    _tlpoints: null,

    /**
     * bottom/right line points
     *  @private
     */
    _blpoints: null,

    /**
     * first day of month
     *  @private
     */
    _firstDayOfMonth: null,

    /**
     * first day point of month
     *  @private
     */
    _firstDayPoints: null,

    render: function (calendarModel, ecModel, api) {

        var group = this.group;

        group.removeAll();

        var coordSys = calendarModel.coordinateSystem;

        // range info
        var rangeData = coordSys.getRangeInfo();
        var orient = coordSys.getOrient();

        this._renderDayRect(calendarModel, rangeData, group);

        // _renderLines must be called prior to following function
        this._renderLines(calendarModel, rangeData, orient, group);

        this._renderYearText(calendarModel, rangeData, orient, group);

        this._renderMonthText(calendarModel, orient, group);

        this._renderWeekText(calendarModel, rangeData, orient, group);
    },

    // render day rect
    _renderDayRect: function (calendarModel, rangeData, group) {
        var coordSys = calendarModel.coordinateSystem;
        var itemRectStyleModel = calendarModel.getModel('itemStyle').getItemStyle();
        var sw = coordSys.getCellWidth();
        var sh = coordSys.getCellHeight();

        for (var i = rangeData.start.time;
            i <= rangeData.end.time;
            i = coordSys.getNextNDay(i, 1).time
        ) {

            var point = coordSys.dataToRect([i], false).tl;

            // every rect
            var rect = new graphic.Rect({
                shape: {
                    x: point[0],
                    y: point[1],
                    width: sw,
                    height: sh
                },
                cursor: 'default',
                style: itemRectStyleModel
            });

            group.add(rect);
        }

    },

    // render separate line
    _renderLines: function (calendarModel, rangeData, orient, group) {

        var self = this;

        var coordSys = calendarModel.coordinateSystem;

        var lineStyleModel = calendarModel.getModel('splitLine.lineStyle').getLineStyle();
        var show = calendarModel.get('splitLine.show');

        var lineWidth = lineStyleModel.lineWidth;

        this._tlpoints = [];
        this._blpoints = [];
        this._firstDayOfMonth = [];
        this._firstDayPoints = [];


        var firstDay = rangeData.start;

        for (var i = 0; firstDay.time <= rangeData.end.time; i++) {
            addPoints(firstDay.formatedDate);

            if (i === 0) {
                firstDay = coordSys.getDateInfo(rangeData.start.y + '-' + rangeData.start.m);
            }

            var date = firstDay.date;
            date.setMonth(date.getMonth() + 1);
            firstDay = coordSys.getDateInfo(date);
        }

        addPoints(coordSys.getNextNDay(rangeData.end.time, 1).formatedDate);

        function addPoints(date) {

            self._firstDayOfMonth.push(coordSys.getDateInfo(date));
            self._firstDayPoints.push(coordSys.dataToRect([date], false).tl);

            var points = self._getLinePointsOfOneWeek(calendarModel, date, orient);

            self._tlpoints.push(points[0]);
            self._blpoints.push(points[points.length - 1]);

            show && self._drawSplitline(points, lineStyleModel, group);
        }


        // render top/left line
        show && this._drawSplitline(self._getEdgesPoints(self._tlpoints, lineWidth, orient), lineStyleModel, group);

        // render bottom/right line
        show && this._drawSplitline(self._getEdgesPoints(self._blpoints, lineWidth, orient), lineStyleModel, group);

    },

    // get points at both ends
    _getEdgesPoints: function (points, lineWidth, orient) {
        var rs = [points[0].slice(), points[points.length - 1].slice()];
        var idx = orient === 'horizontal' ? 0 : 1;

        // both ends of the line are extend half lineWidth
        rs[0][idx] = rs[0][idx] - lineWidth / 2;
        rs[1][idx] = rs[1][idx] + lineWidth / 2;

        return rs;
    },

    // render split line
    _drawSplitline: function (points, lineStyleModel, group) {

        var poyline = new graphic.Polyline({
            z2: 20,
            shape: {
                points: points
            },
            style: lineStyleModel
        });

        group.add(poyline);
    },

    // render month line of one week points
    _getLinePointsOfOneWeek: function (calendarModel, date, orient) {

        var coordSys = calendarModel.coordinateSystem;
        date = coordSys.getDateInfo(date);

        var points = [];

        for (var i = 0; i < 7; i++) {

            var tmpD = coordSys.getNextNDay(date.time, i);
            var point = coordSys.dataToRect([tmpD.time], false);

            points[2 * tmpD.day] = point.tl;
            points[2 * tmpD.day + 1] = point[orient === 'horizontal' ? 'bl' : 'tr'];
        }

        return points;

    },

    _formatterLabel: function (formatter, params) {

        if (typeof formatter === 'string' && formatter) {
            return formatUtil.formatTplSimple(formatter, params);
        }

        if (typeof formatter === 'function') {
            return formatter(params);
        }

        return params.nameMap;

    },

    _yearTextPositionControl: function (textEl, point, orient, position, margin) {

        point = point.slice();
        var aligns = ['center', 'bottom'];

        if (position === 'bottom') {
            point[1] += margin;
            aligns = ['center', 'top'];
        }
        else if (position === 'left') {
            point[0] -= margin;
        }
        else if (position === 'right') {
            point[0] += margin;
            aligns = ['center', 'top'];
        }
        else { // top
            point[1] -= margin;
        }

        var rotate = 0;
        if (position === 'left' || position === 'right') {
            rotate = Math.PI / 2;
        }

        return {
            rotation: rotate,
            position: point,
            style: {
                textAlign: aligns[0],
                textVerticalAlign: aligns[1]
            }
        };
    },

    // render year
    _renderYearText: function (calendarModel, rangeData, orient, group) {
        var yearLabel = calendarModel.getModel('yearLabel');

        if (!yearLabel.get('show')) {
            return;
        }

        var margin = yearLabel.get('margin');
        var pos = yearLabel.get('position');

        if (!pos) {
            pos = orient !== 'horizontal' ? 'top' : 'left';
        }

        var points = [this._tlpoints[this._tlpoints.length - 1], this._blpoints[0]];
        var xc = (points[0][0] + points[1][0]) / 2;
        var yc = (points[0][1] + points[1][1]) / 2;

        var idx = orient === 'horizontal' ? 0 : 1;

        var posPoints = {
            top: [xc, points[idx][1]],
            bottom: [xc, points[1 - idx][1]],
            left: [points[1 - idx][0], yc],
            right: [points[idx][0], yc]
        };

        var name = rangeData.start.y;

        if (+rangeData.end.y > +rangeData.start.y) {
            name = name + '-' + rangeData.end.y;
        }

        var formatter = yearLabel.get('formatter');

        var params = {
            start: rangeData.start.y,
            end: rangeData.end.y,
            nameMap: name
        };

        var content = this._formatterLabel(formatter, params);

        var yearText = new graphic.Text({z2: 30});
        graphic.setTextStyle(yearText.style, yearLabel, {text: content}),
        yearText.attr(this._yearTextPositionControl(yearText, posPoints[pos], orient, pos, margin));

        group.add(yearText);
    },

    _monthTextPositionControl: function (point, isCenter, orient, position, margin) {
        var align = 'left';
        var vAlign = 'top';
        var x = point[0];
        var y = point[1];

        if (orient === 'horizontal') {
            y = y + margin;

            if (isCenter) {
                align = 'center';
            }

            if (position === 'start') {
                vAlign = 'bottom';
            }
        }
        else {
            x = x + margin;

            if (isCenter) {
                vAlign = 'middle';
            }

            if (position === 'start') {
                align = 'right';
            }
        }

        return {
            x: x,
            y: y,
            textAlign: align,
            textVerticalAlign: vAlign
        };
    },

    // render month and year text
    _renderMonthText: function (calendarModel, orient, group) {
        var monthLabel = calendarModel.getModel('monthLabel');

        if (!monthLabel.get('show')) {
            return;
        }

        var nameMap = monthLabel.get('nameMap');
        var margin = monthLabel.get('margin');
        var pos = monthLabel.get('position');
        var align = monthLabel.get('align');

        var termPoints = [this._tlpoints, this._blpoints];

        if (zrUtil.isString(nameMap)) {
            nameMap = MONTH_TEXT[nameMap.toUpperCase()] || [];
        }

        var idx = pos === 'start' ? 0 : 1;
        var axis = orient === 'horizontal' ? 0 : 1;
        margin = pos === 'start' ? -margin : margin;
        var isCenter = (align === 'center');

        for (var i = 0; i < termPoints[idx].length - 1; i++) {

            var tmp = termPoints[idx][i].slice();
            var firstDay = this._firstDayOfMonth[i];

            if (isCenter) {
                var firstDayPoints = this._firstDayPoints[i];
                tmp[axis] = (firstDayPoints[axis] + termPoints[0][i + 1][axis]) / 2;
            }

            var formatter = monthLabel.get('formatter');
            var name = nameMap[+firstDay.m - 1];
            var params = {
                yyyy: firstDay.y,
                yy: (firstDay.y + '').slice(2),
                MM: firstDay.m,
                M: +firstDay.m,
                nameMap: name
            };

            var content = this._formatterLabel(formatter, params);

            var monthText = new graphic.Text({z2: 30});
            zrUtil.extend(
                graphic.setTextStyle(monthText.style, monthLabel, {text: content}),
                this._monthTextPositionControl(tmp, isCenter, orient, pos, margin)
            );

            group.add(monthText);
        }
    },

    _weekTextPositionControl: function (point, orient, position, margin, cellSize) {
        var align = 'center';
        var vAlign = 'middle';
        var x = point[0];
        var y = point[1];
        var isStart = position === 'start';

        if (orient === 'horizontal') {
            x = x + margin + (isStart ? 1 : -1) * cellSize[0] / 2;
            align = isStart ? 'right' : 'left';
        }
        else {
            y = y + margin + (isStart ? 1 : -1) * cellSize[1] / 2;
            vAlign = isStart ? 'bottom' : 'top';
        }

        return {
            x: x,
            y: y,
            textAlign: align,
            textVerticalAlign: vAlign
        };
    },

    // render weeks
    _renderWeekText: function (calendarModel, rangeData, orient, group) {
        var dayLabel = calendarModel.getModel('dayLabel');

        if (!dayLabel.get('show')) {
            return;
        }

        var coordSys = calendarModel.coordinateSystem;
        var pos = dayLabel.get('position');
        var nameMap = dayLabel.get('nameMap');
        var margin = dayLabel.get('margin');
        var firstDayOfWeek = coordSys.getFirstDayOfWeek();

        if (zrUtil.isString(nameMap)) {
            nameMap = WEEK_TEXT[nameMap.toUpperCase()] || [];
        }

        var start = coordSys.getNextNDay(
            rangeData.end.time, (7 - rangeData.lweek)
        ).time;

        var cellSize = [coordSys.getCellWidth(), coordSys.getCellHeight()];
        margin = numberUtil.parsePercent(margin, cellSize[orient === 'horizontal' ? 0 : 1]);

        if (pos === 'start') {
            start = coordSys.getNextNDay(
                rangeData.start.time, -(7 + rangeData.fweek)
            ).time;
            margin = -margin;
        }

        for (var i = 0; i < 7; i++) {

            var tmpD = coordSys.getNextNDay(start, i);
            var point = coordSys.dataToRect([tmpD.time], false).center;
            var day = i;
            day = Math.abs((i + firstDayOfWeek) % 7);
            var weekText = new graphic.Text({z2: 30});

            zrUtil.extend(
                graphic.setTextStyle(weekText.style, dayLabel, {text: nameMap[day]}),
                this._weekTextPositionControl(point, orient, pos, margin, cellSize)
            );
            group.add(weekText);
        }
    }
});