/*
 * Copyright (C) 2016 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

// CircleChart creates a donut/pie chart of colored sections.
//
// Initialize the chart with a size and inner radius to get a blank chart.
// To populate with data, first initialize the segments. The class names you
// provide for the segments will allow you to style them. You can then update
// the chart with new values (in the same order as the segments) at any time.
//
// SVG:
//
// - There is a single background path for the background.
// - There is a path for each segment.
// - If you want to put something inside the middle of the chart you can use `centerElement`.
//
//  <div class="circle-chart">
//      <svg width="120" height="120" viewbox="0 0 120 120">
//          <path class="background" d="..."/>
//          <path class="segment segment-class-name-1" d="..."/>
//          <path class="segment segment-class-name-2" d="..."/>
//          ...
//      </svg>
//      <div class="center"></div>
//  </div>

WebInspector.CircleChart = class CircleChart
{
    constructor({size, innerRadiusRatio})
    {
        this._data = [];
        this._size = size;
        this._radius = (size / 2) - 1;
        this._innerRadius = innerRadiusRatio ? Math.floor(this._radius * innerRadiusRatio) : 0;

        this._element = document.createElement("div");
        this._element.classList.add("circle-chart");

        this._chartElement = this._element.appendChild(createSVGElement("svg"));
        this._chartElement.setAttribute("width", size);
        this._chartElement.setAttribute("height", size);
        this._chartElement.setAttribute("viewbox", `0 0 ${size} ${size}`);

        this._pathElements = [];
        this._values = [];
        this._total = 0;

        let backgroundPath = this._chartElement.appendChild(createSVGElement("path"));
        backgroundPath.setAttribute("d", this._createCompleteCirclePathData(this.size / 2, this._radius, this._innerRadius));
        backgroundPath.classList.add("background");
    }

    // Public

    get element() { return this._element; }
    get points() { return this._points; }
    get size() { return this._size; }

    get centerElement()
    {
        if (!this._centerElement) {
            this._centerElement = this._element.appendChild(document.createElement("div"));
            this._centerElement.classList.add("center");
            this._centerElement.style.width = this._centerElement.style.height = this._radius + "px";
            this._centerElement.style.top = this._centerElement.style.left = (this._radius - this._innerRadius) + "px";
        }

        return this._centerElement;
    }

    get segments()
    {
        return this._segments;
    }

    set segments(segmentClassNames)
    {
        for (let pathElement of this._pathElements)
            pathElement.remove();

        this._pathElements = [];

        for (let className of segmentClassNames) {
            let pathElement = this._chartElement.appendChild(createSVGElement("path"));
            pathElement.classList.add("segment", className);
            this._pathElements.push(pathElement);
        }
    }

    get values()
    {
        return this._values;
    }

    set values(values)
    {
        console.assert(values.length === this._pathElements.length, "Should have the same number of values as segments");

        this._values = values;
        this._total = 0;

        for (let value of values)
            this._total += value;
    }

    clear()
    {
        this.values = new Array(this._values.length).fill(0);
    }

    needsLayout()
    {
        if (this._scheduledLayoutUpdateIdentifier)
            return;

        this._scheduledLayoutUpdateIdentifier = requestAnimationFrame(this.updateLayout.bind(this));
    }

    updateLayout()
    {
        if (this._scheduledLayoutUpdateIdentifier) {
            cancelAnimationFrame(this._scheduledLayoutUpdateIdentifier);
            this._scheduledLayoutUpdateIdentifier = undefined;
        }

        if (!this._values.length)
            return;

        const center = this._size / 2;
        let startAngle = -Math.PI / 2;
        let endAngle = 0;

        for (let i = 0; i < this._values.length; ++i) {
            let value = this._values[i];
            let pathElement = this._pathElements[i];

            if (value === 0)
                pathElement.removeAttribute("d");
            else if (value === this._total)
                pathElement.setAttribute("d", this._createCompleteCirclePathData(center, this._radius, this._innerRadius));
            else {
                let angle = (value / this._total) * Math.PI * 2;
                endAngle = startAngle + angle;

                pathElement.setAttribute("d", this._createSegmentPathData(center, startAngle, endAngle, this._radius, this._innerRadius));
                startAngle = endAngle;
            }
        }
    }

    // Private

    _createCompleteCirclePathData(c, r1, r2)
    {
        const a1 = 0;
        const a2 = Math.PI * 1.9999;
        let x1 = c + Math.cos(a1) * r1,
            y1 = c + Math.sin(a1) * r1,
            x2 = c + Math.cos(a2) * r1,
            y2 = c + Math.sin(a2) * r1,
            x3 = c + Math.cos(a2) * r2,
            y3 = c + Math.sin(a2) * r2,
            x4 = c + Math.cos(a1) * r2,
            y4 = c + Math.sin(a1) * r2;
        return [
            "M", x1, y1,                    // Starting position.
            "A", r1, r1, 0, 1, 1, x2, y2,   // Draw outer arc.
            "Z",                            // Close path.
            "M", x3, y3,                    // Starting position.
            "A", r2, r2, 0, 1, 0, x4, y4,   // Draw inner arc.
            "Z"                             // Close path.
        ].join(" ");
    }

    _createSegmentPathData(c, a1, a2, r1, r2)
    {
        const largeArcFlag = ((a2 - a1) % (Math.PI * 2)) > Math.PI ? 1 : 0;
        let x1 = c + Math.cos(a1) * r1,
            y1 = c + Math.sin(a1) * r1,
            x2 = c + Math.cos(a2) * r1,
            y2 = c + Math.sin(a2) * r1,
            x3 = c + Math.cos(a2) * r2,
            y3 = c + Math.sin(a2) * r2,
            x4 = c + Math.cos(a1) * r2,
            y4 = c + Math.sin(a1) * r2;
        return [
            "M", x1, y1,                                // Starting position.
            "A", r1, r1, 0, largeArcFlag, 1, x2, y2,    // Draw outer arc.
            "L", x3, y3,                                // Connect outer and innner arcs.
            "A", r2, r2, 0, largeArcFlag, 0, x4, y4,    // Draw inner arc.
            "Z"                                         // Close path.
        ].join(" ");
    }
};
