/////////////////////////////////////////////////////////////////////////
// Copyright 2011-2018 Simon Schneegans
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/////////////////////////////////////////////////////////////////////////

using GLib.Math;

namespace GnomePie {

/////////////////////////////////////////////////////////////////////////
///  Renders the center of a Pie.
/////////////////////////////////////////////////////////////////////////

public class CenterRenderer : GLib.Object {

    /////////////////////////////////////////////////////////////////////
    /// The PieRenderer which owns this CenterRenderer.
    /////////////////////////////////////////////////////////////////////

    private unowned PieRenderer parent;

    /////////////////////////////////////////////////////////////////////
    /// The caption drawn in the center. Changes when the active slice
    /// changes.
    /////////////////////////////////////////////////////////////////////

    private unowned Image? caption;

    /////////////////////////////////////////////////////////////////////
    /// The color of the currently active slice. Used to colorize layers.
    /////////////////////////////////////////////////////////////////////

    private Color color;

    /////////////////////////////////////////////////////////////////////
    /// Two AnimatedValues: alpha is for global transparency (when
    /// fading in/out), activity is 1.0 if there is an active slice and
    /// 0.0 if there is no active slice.
    /////////////////////////////////////////////////////////////////////

    private AnimatedValue activity;
    private AnimatedValue alpha;

    /////////////////////////////////////////////////////////////////////
    /// C'tor, initializes all members.
    /////////////////////////////////////////////////////////////////////

    public CenterRenderer(PieRenderer parent) {
        this.parent = parent;
        this.activity = new AnimatedValue.linear(0.0, 0.0, Config.global.theme.transition_time);
        this.alpha = new AnimatedValue.linear(0.0, 1.0, Config.global.theme.fade_in_time);
        this.color = new Color();
        this.caption = null;
    }

    /////////////////////////////////////////////////////////////////////
    /// Initiates the fade-out animation by resetting the targets of the
    /// AnimatedValues to 0.0.
    /////////////////////////////////////////////////////////////////////

    public void fade_out() {
        this.activity.reset_target(0.0, Config.global.theme.fade_out_time);
        this.alpha.reset_target(0.0, Config.global.theme.fade_out_time);
    }

    /////////////////////////////////////////////////////////////////////
    /// Should be called if the active slice of the PieRenderer changes.
    /// The members activity, caption and color are set accordingly.
    /////////////////////////////////////////////////////////////////////

    public void set_active_slice(SliceRenderer? active_slice) {
        if (active_slice == null) {
            this.activity.reset_target(0.0, Config.global.theme.transition_time);
        } else {
            this.activity.reset_target(1.0, Config.global.theme.transition_time);
            this.caption = active_slice.caption;
            this.color   = active_slice.color;
        }
    }

    /////////////////////////////////////////////////////////////////////
    /// Draws all center layers and the caption.
    /////////////////////////////////////////////////////////////////////

    public void draw(double frame_time, Cairo.Context ctx, double angle, int slice_track) {
        // get all center_layers
        var layers = Config.global.theme.center_layers;

        // update the AnimatedValues
        this.activity.update(frame_time);
        this.alpha.update(frame_time);

        // draw each layer
        foreach (var layer in layers) {
            ctx.save();

            // calculate all values needed for animation/drawing
            double max_scale = layer.active_scale*this.activity.val
                + layer.inactive_scale*(1.0-this.activity.val);
            double max_alpha = layer.active_alpha*this.activity.val
                + layer.inactive_alpha*(1.0-this.activity.val);
            double colorize = ((layer.active_colorize == true) ? this.activity.val : 0.0)
                + ((layer.inactive_colorize == true) ? 1.0 - this.activity.val : 0.0);
            double max_rotation_speed = layer.active_rotation_speed*this.activity.val
                + layer.inactive_rotation_speed*(1.0-this.activity.val);
            CenterLayer.RotationMode rotation_mode = ((this.activity.val > 0.5) ?
                layer.active_rotation_mode : layer.inactive_rotation_mode);

            double direction = 0;

            if (rotation_mode == CenterLayer.RotationMode.TO_MOUSE) {
                direction = angle;

            } else if (rotation_mode == CenterLayer.RotationMode.TO_ACTIVE) {
                double slice_angle = parent.total_slice_count > 0 ? 2*PI/parent.total_slice_count : 0;
                direction = (int)((angle+0.5*slice_angle) / (slice_angle))*slice_angle;

            } else if (rotation_mode == CenterLayer.RotationMode.TO_SECOND) {
                var now = new DateTime.now_local();
                direction = 2*PI*(now.get_second()+60-15)/60;

            } else if (rotation_mode == CenterLayer.RotationMode.TO_MINUTE) {
                var now = new DateTime.now_local();
                direction = 2*PI*(now.get_minute()+60-15)/60;

            } else if (rotation_mode == CenterLayer.RotationMode.TO_HOUR_24) {
                var now = new DateTime.now_local();
                direction = 2*PI*(now.get_hour()+24-6)/24 + 2*PI*(now.get_minute())/(60*24);

            } else if (rotation_mode == CenterLayer.RotationMode.TO_HOUR_12) {
                var now = new DateTime.now_local();
                direction = 2*PI*(now.get_hour()+12-3)/12 + 2*PI*(now.get_minute())/(60*12);
            }

            if (rotation_mode == CenterLayer.RotationMode.AUTO) {
                layer.rotation += max_rotation_speed*frame_time;
            } else {
                direction = Math.fmod(direction, 2*PI);
                double diff = direction-layer.rotation;
                double smoothy = fabs(diff) < 0.9 ? fabs(diff) + 0.1 : 1.0;
                double step = max_rotation_speed*frame_time*smoothy;

                if (fabs(diff) <= step || fabs(diff) >= 2.0*PI - step)
                    layer.rotation = direction;
                else {
                    if ((diff > 0 && diff < PI) || diff < -PI) layer.rotation += step;
                    else                                       layer.rotation -= step;
                }
            }

            layer.rotation = fmod(layer.rotation+2*PI, 2*PI);

            if (colorize > 0.0) ctx.push_group();

            // transform the context
            ctx.rotate(layer.rotation);
            ctx.scale(max_scale, max_scale);

            // paint the layer
            layer.image.paint_on(ctx, this.alpha.val*max_alpha);

            // colorize it, if necessary
            if (colorize > 0.0) {
                ctx.set_operator(Cairo.Operator.ATOP);
                ctx.set_source_rgb(this.color.r, this.color.g, this.color.b);
                ctx.paint_with_alpha(colorize);

                ctx.set_operator(Cairo.Operator.OVER);
                ctx.pop_group_to_source();
                ctx.paint();
            }

            ctx.restore();
        }

        // draw caption
        if (Config.global.theme.caption && caption != null && this.activity.val > 0) {
            ctx.save();
            double x, y;
            ctx.get_current_point(out x, out y);
            ctx.move_to(GLib.Math.floor(x), GLib.Math.floor(y));
            caption.paint_on(ctx, this.activity.val*this.alpha.val);
            ctx.restore();
        }

        //scroll pie
        if (this.alpha.val > 0.1
            && this.parent.original_visible_slice_count < this.parent.slice_count()
            && this.parent.original_visible_slice_count > 0) {
            int np= (this.parent.slice_count()+this.parent.original_visible_slice_count -1)/this.parent.original_visible_slice_count;
            int cp= this.parent.first_slice_idx / this.parent.original_visible_slice_count;
            int r= 8;       //circle radious
            int dy= 20;     //distance between circles
            double a= 0.8 * this.alpha.val;
            int dx= (int)Config.global.theme.center_radius + r + 10;
            if (this.parent.center_x + dx > this.parent.size_w)
                dx= -dx;    //no right side, put scroll in the left size
            ctx.save();
            ctx.identity_matrix();
            ctx.translate(this.parent.center_x + dx, this.parent.center_y - (np-1)*dy/2);
            for (int i=0; i<np; i++) {
                ctx.arc( 0, 0, r, 0, 2*PI );
                if (i == cp){
                    ctx.set_source_rgba(0.3,0.3,0.3, a);    //light gray stroke
                    ctx.stroke_preserve();
                    ctx.set_source_rgba(1,1,1, a);          //white fill
                    ctx.fill(); //current
                } else {
                    ctx.set_source_rgba(1,1,1, a);          //white stroke
                    ctx.stroke_preserve();
                    ctx.set_source_rgba(0.3,0.3,0.3, a/4);  //light gray fill
                    ctx.fill(); //current
                }
                ctx.translate(0, dy);
            }
            ctx.restore();
        }
    }
}

}
