declare module D3 {
    export module Time {
        export interface Time {        
            weekOfYear(x: any): any;//this is missin from d3.d.ts
        }
    }
}

module powerbi.visuals {
    export interface DateValue {
        date: Date;
        value: number;
    };
    export interface CalendarViewModel {
        values: DateValue[];
    };

    export class CalendarVisual implements IVisual {
        public static capabilities: VisualCapabilities = {
            dataRoles: [
                {
                    name: 'Category',
                    kind: VisualDataRoleKind.Grouping,
                },
                {
                    name: 'Y',
                    kind: VisualDataRoleKind.Measure,
                },
            ],
            dataViewMappings: [{
                categorical: {
                    categories: {
                        for: { in: 'Category' },
                    },
                    values: {
                        for: { in: 'Y' }
                    },
                    rowCount: { preferred: { max: 2 } }
                },
            }],
            dataPoint: {
                displayName: data.createDisplayNameGetter('Visual_DataPoint'),
                properties: {
                    fill: {
                        displayName: data.createDisplayNameGetter('Visual_Fill'),
                        type: { fill: { solid: { color: true } } }
                    },
                }
            },
            labels: {
                displayName: data.createDisplayNameGetter('Visual_DataPointsLabels'),
                properties: {
                    show: {
                        displayName: data.createDisplayNameGetter('Visual_Show'),
                        type: { bool: true }
                    },
                    color: {
                        displayName: data.createDisplayNameGetter('Visual_LabelsFill'),
                        type: { fill: { solid: { color: true } } }
                    },
                    labelDisplayUnits: {
                        displayName: data.createDisplayNameGetter('Visual_DisplayUnits'),
                        type: { formatting: { labelDisplayUnits: true } }
                    }
                }
            }
        };
        
        private drawMonthPath = false;
        private drawLegend = false;
        private drawLabels = true;
        private width = 1016;
        private height = 144;
        private cellSize = 18; // cell size
        private element: HTMLElement;
        private rect: D3.Selection;

        constructor(cellSizeOpt?: number)
        {
            if (cellSizeOpt) {
                this.cellSize = cellSizeOpt;
            }
        }

        public init(options: VisualInitOptions) {
            this.element = options.element.get(0);
        }

        public update(options: VisualUpdateOptions) {
            d3.select(this.element).selectAll("*").remove();
            var viewModel = this.convert(options.dataViews[0]);

	    if (viewModel == null) return;

            var maxDomain = Math.max.apply(Math,
                viewModel.values.map((v) => {
                    return v.value;
                })
            );
            this.draw(this.element, options.viewport.width, options.viewport.height, this.getYears(viewModel), maxDomain);
            this.apply(viewModel, maxDomain);
        }
        
        private draw(element, itemWidth: number, itemHeight: number, range: number[], maxDomain: number)
        {
            var format = d3.time.format("%Y-%m-%d");
            
            var svg = d3.select(element).selectAll("svg")
                .data(range)
                .enter().append("svg")
                .attr("width", itemWidth)
                .attr("height", itemWidth / 7)
                .attr("viewBox", "0 0 " + this.width + " " + this.height)
                .append("g")
                .attr("transform", "translate(" + ((this.width - this.cellSize * 52) / 2) + "," + (this.height - this.cellSize * 7 - 1) + ")");

            if (this.drawLabels) {
                var textGroup = svg.append("g").attr("fill", "#cccccc");
                textGroup.append("text")
                    .attr("transform", "translate(" + this.cellSize * -1.5 + "," + this.cellSize * 3.5 + ")rotate(-90)")
                    .style("text-anchor", "middle")
                    .text(function (d) { return d; });

                textGroup.append("text")
                    .style("text-anchor", "middle")
                    .text("M")
                    .attr("transform", "translate(" + this.cellSize * -0.75 + ")")
                    .attr("x", 0)
                    .attr("y", 2 * this.cellSize);

                textGroup.append("text")
                    .style("text-anchor", "middle")
                    .text("W")
                    .attr("transform", "translate(" + this.cellSize * -0.75 + ")")
                    .attr("x", 0)
                    .attr("y", 4 * this.cellSize);

                textGroup.append("text")
                    .style("text-anchor", "middle")
                    .text("F")
                    .attr("transform", "translate(" + this.cellSize * -0.75 + ")")
                    .attr("x", 0)
                    .attr("y", 6 * this.cellSize);

                textGroup.append("text")
                    .attr("transform", "translate(" + (this.width - (3 * this.cellSize)) + "," + this.cellSize * 3.5 + ")rotate(90)")
                    .style("text-anchor", "middle")
                    .text(function (d) { return d; });

                textGroup.selectAll(".month")
                    .data((d) => { return d3.time.months(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
                    .enter()
                    .append("text")
                    .attr("transform", (d) => { return "translate(" + d3.time.weekOfYear(d) * this.cellSize + ", -5)"; })
                    .text((d) => { return d3.time.format("%b")(d); });
            }

            this.rect = svg.selectAll(".day")
                .data(this.getDaysOfYear)
                .enter().append("rect")
                .attr("width", this.cellSize)
                .attr("height", this.cellSize)
                .attr("class", "day")
                .attr("style", "fill: #eeeeee; stroke-width: 2px; stroke: #ffffff")
                .attr("x", this.getXPosition)
                .attr("y", this.getYPosition)
                .datum(format);

            this.rect.append("title")
                .text(function (d) { return d; });

            if (this.drawMonthPath) {
                svg.selectAll(".month")
                    .data(function (d) { return d3.time.months(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
                    .enter().append("path")
                    .attr("class", "month")
                    .attr("d", this.monthPath)
                    .attr("stroke", "#cccccc");
            }

            if (this.drawLegend) {
                var legendGroup = d3.select(this.element).insert("svg", ":first-child")
                    .attr("width", itemWidth)
                    .attr("height", itemWidth / 17.5)
                    .attr("viewBox", "0 0 " + this.width + " " + this.height / 7)
                    .attr("preserveAspectRatio", "xMinYMin")
                    .append("g");

                legendGroup.append("rect")
                    .attr("width", this.cellSize)
                    .attr("height", this.cellSize)
                    .attr("x", 0).attr("y", 0)
                    .attr("fill", "#000000");

                legendGroup.append("rect")
                    .attr("width", this.cellSize)
                    .attr("height", this.cellSize)
                    .attr("x", 0).attr("y", this.cellSize * 1.5)
                    .attr("fill", "#00ff00");

                legendGroup
                    .append("text").text(0)
                    .attr("x", this.cellSize * 2).attr("y", this.cellSize);
                legendGroup
                    .append("text").text(d3.format(".4r")(maxDomain))
                    .attr("x", this.cellSize * 2).attr("y", this.cellSize * 2.5);
            }
        }

        private apply(viewModel: CalendarViewModel, maxDomain: number)
        {            
            var pad = (n: any) => {
                if (n.toString().length === 1) {
                    return "0" + n;
                }

                return n.toString();
            };

            var quantizeColor =
                d3.scale.quantize()
                    .domain([0, maxDomain])
                    .range(d3.range(256).map(function (d) { return "#00" + pad(d.toString(16)) + "00"; }));

            
            var data = d3.nest()
                .key(function (d: DateValue) { return d.date.getFullYear() + "-" + pad(d.date.getMonth()) + "-" + pad(d.date.getDate()); })
                .rollup(function (d: DateValue[]) { return d.map((dateValue) => { return dateValue.value; }).reduce((prev, curr) => prev + curr);  })
                .map(viewModel.values);

            this.rect.filter(function (d) { return d in data; })
                .attr("style", function (d) { return "fill:" + quantizeColor(data[d]); })
                .select("title")
                .text(function (d) { return d + ": " + d3.format(".6f")(data[d]); });
        }

        private convert(dataView: DataView): CalendarViewModel {
	    if (dataView.categorical.categories == null) {
  	     window.console.log("no categoricals"); return;
	    }

            var returnSet = dataView.categorical.categories[0].values.map(
                (v, i) => {
                    return <DateValue> {
                        date: v,
                        value: dataView.categorical.values.map((val) => { return val.values[i]; })
                            .reduce((prev, curr) => { return prev + curr; })
                    };
            });

            return <CalendarViewModel> {
                values: returnSet
            };
        }
        public getYears(viewModel: CalendarViewModel) {
            var allYears = viewModel.values.map((value) => { 
		if (value.date == null || isNaN(Date.parse(value.date.toString())))
		 { 
			return 1900; 
		 }; 
		return value.date.getFullYear(); 
	    });
            var uniqueYears = {}, a = [];
            for (var i = 0, l = allYears.length; i < l; ++i) {
                if (uniqueYears.hasOwnProperty(allYears[i].toString())) {
                    continue;
                }
                a.push(allYears[i]);
                uniqueYears[allYears[i].toString()] = 1;
            }
            return a.sort();
        }
        private getDaysOfYear = (year: number) => { return d3.time.days(new Date(year, 0, 1), new Date(year + 1, 0, 1)); };
        public getXPosition = (date: Date) => { return d3.time.weekOfYear(date) * this.cellSize; };
        public getYPosition = (date: Date) => { return date.getDay() * this.cellSize; };
        private monthPath = (t0) => {
            var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0), d0 = t0.getDay(), w0 = d3.time.weekOfYear(t0), d1 = t1.getDay(), w1 = d3.time.weekOfYear(t1);
            return "M" + (w0 + 1) * this.cellSize + "," + d0 * this.cellSize + "H" + w0 * this.cellSize + "V" + 7 * this.cellSize + "H" + w1 * this.cellSize + "V" + (d1 + 1) * this.cellSize + "H" + (w1 + 1) * this.cellSize + "V" + 0 + "H" + (w0 + 1) * this.cellSize + "Z";
        };
    }
} 

module powerbi.visuals.plugins {
	export var _CalendarVisual: IVisualPlugin = {
		name: '_CalendarVisual',
		class: '_CalendarVisual',
		capabilities: CalendarVisual.capabilities,
		create: () => new CalendarVisual()
	};
}