/////////////////////////////////////////////////////////////////////////
// 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 a Slice of a Pie. According to the current theme.
/////////////////////////////////////////////////////////////////////////

public class SliceRenderer : GLib.Object {

    /////////////////////////////////////////////////////////////////////
    /// Whether this slice is active (hovered) or not.
    /////////////////////////////////////////////////////////////////////

    public bool active {get; private set; default = false;}

    /////////////////////////////////////////////////////////////////////
    /// The Image which should be displayed as center caption when this
    /// slice is active.
    /////////////////////////////////////////////////////////////////////

    public Image caption {get; private set;}

    /////////////////////////////////////////////////////////////////////
    /// The color which should be used for colorizing center layers when
    /// this slice is active.
    /////////////////////////////////////////////////////////////////////

    public Color color {get; private set;}

    /////////////////////////////////////////////////////////////////////
    /// The Action which is rendered by this SliceRenderer.
    /////////////////////////////////////////////////////////////////////

    public Action action;

    /////////////////////////////////////////////////////////////////////
    /// The two Images used, when this slice is active or not.
    /////////////////////////////////////////////////////////////////////

    private Image active_icon;
    private Image inactive_icon;

    /////////////////////////////////////////////////////////////////////
    /// The Image displaying the associated hot key of this slice.
    /////////////////////////////////////////////////////////////////////

    private Image hotkey;

    /////////////////////////////////////////////////////////////////////
    /// The PieRenderer which owns this SliceRenderer.
    /////////////////////////////////////////////////////////////////////

    private unowned PieRenderer parent;

    /////////////////////////////////////////////////////////////////////
    /// The index of this slice in a pie. Clockwise assigned, starting
    /// from the right-most slice.
    /////////////////////////////////////////////////////////////////////

    private int position;

    /////////////////////////////////////////////////////////////////////
    /// AnimatedValues needed for a slice.
    /////////////////////////////////////////////////////////////////////

    private AnimatedValue fade;             // for transitions from active to inactive
    private AnimatedValue scale;            // for zoom effect
    private AnimatedValue alpha;            // for fading in/out
    private AnimatedValue fade_rotation;    // for fading in/out
    private AnimatedValue fade_scale;       // for fading in/out
    private AnimatedValue wobble;           // for organic wobbling

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

    public SliceRenderer(PieRenderer parent) {
        this.parent = parent;
        this.reset_anim();
    }

    /////////////////////////////////////////////////////////////////////
    /// Put all AnimatedValues in their initial values
    /////////////////////////////////////////////////////////////////////

    public void reset_anim() {
        this.fade =   new AnimatedValue.linear(0.0, 0.0, Config.global.theme.transition_time);
        this.wobble = 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.scale =  new AnimatedValue.cubic(AnimatedValue.Direction.OUT,
                                                 1.0/Config.global.theme.max_zoom,
                                                 1.0/Config.global.theme.max_zoom,
                                                 Config.global.theme.transition_time,
                                                 Config.global.theme.springiness);
        this.fade_scale = new AnimatedValue.cubic(AnimatedValue.Direction.OUT,
                                                 Config.global.theme.fade_in_zoom, 1.0,
                                                 Config.global.theme.fade_in_time,
                                                 Config.global.theme.springiness);
        this.fade_rotation = new AnimatedValue.cubic(AnimatedValue.Direction.OUT,
                                                 Config.global.theme.fade_in_rotation, 0.0,
                                                 Config.global.theme.fade_in_time);
    }

    /////////////////////////////////////////////////////////////////////
    /// Loads an Action. All members are initialized accordingly.
    /////////////////////////////////////////////////////////////////////

    public void load(Action action, int position) {
        this.position = position;
        this.action = action;


        if (Config.global.theme.caption)
            this.caption = new RenderedText(action.name,
                                            Config.global.theme.caption_width,
                                            Config.global.theme.caption_height,
                                            Config.global.theme.caption_font,
                                            Config.global.theme.caption_color,
                                            Config.global.global_scale);

        this.active_icon = new ThemedIcon(action.name, action.icon, true);
        this.inactive_icon = new ThemedIcon(action.name, action.icon, false);

        this.color = new Color.from_icon(this.active_icon);

        string hotkey_label = "";
        if (position < 10) {
            hotkey_label = "%u".printf((position+1)%10);
        } else if (position < 36) {
            hotkey_label = "%c".printf((char)(55 + position));
        }

        this.hotkey = new RenderedText(hotkey_label, (int)Config.global.theme.slice_radius*2,
                         (int)Config.global.theme.slice_radius*2, "sans 20",
                         new Color(), Config.global.global_scale);
    }

    /////////////////////////////////////////////////////////////////////
    /// Activates the Action of this slice.
    /////////////////////////////////////////////////////////////////////

    public void activate(uint32 time_stamp) {
        action.activate(time_stamp);
    }

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

    public void fade_out() {
        this.alpha.reset_target(0.0, Config.global.theme.fade_out_time);
        this.fade_scale = new AnimatedValue.cubic(AnimatedValue.Direction.IN,
                                             this.fade_scale.val,
                                             Config.global.theme.fade_out_zoom,
                                             Config.global.theme.fade_out_time,
                                             Config.global.theme.springiness);
        this.fade_rotation = new AnimatedValue.cubic(AnimatedValue.Direction.IN,
                                             this.fade_rotation.val,
                                             Config.global.theme.fade_out_rotation,
                                             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 == this) {
            this.fade.reset_target(1.0, Config.global.theme.transition_time);
        } else {
            this.fade.reset_target(0.0, Config.global.theme.transition_time);
        }
    }

    /////////////////////////////////////////////////////////////////////
    /// Draws all layers of the slice.
    /////////////////////////////////////////////////////////////////////

    public void draw(double frame_time, Cairo.Context ctx, double angle, int slice_track) {

        // update the AnimatedValues
        this.scale.update(frame_time);
        this.alpha.update(frame_time);
        this.fade.update(frame_time);
        this.fade_scale.update(frame_time);
        this.fade_rotation.update(frame_time);
        this.wobble.update(frame_time);

        double direction = 2.0 * PI * (position-parent.first_slice_idx)/parent.total_slice_count
                            + parent.first_slice_angle + this.fade_rotation.val;
        double max_scale = 1.0/Config.global.theme.max_zoom;
        double diff = fabs(angle-direction);

        if (diff > 2 * PI) {
            diff = diff - 2 * PI;
        }

        if (diff > PI) {
            diff = 2 * PI - diff;
        }


        active = ((parent.active_slice >= 0) && (diff < PI/parent.total_slice_count));

        if (slice_track != 0) {
            double wobble = Config.global.theme.wobble*diff/PI*(1-diff/PI);
            if ((direction < angle && direction > angle - PI) || direction > PI+angle) {
                this.wobble.reset_target(-wobble, Config.global.theme.transition_time*0.5);
            } else {
                this.wobble.reset_target(wobble, Config.global.theme.transition_time*0.5);
            }
        } else {
            this.wobble.reset_target(0, Config.global.theme.transition_time*0.5);
        }

        direction += this.wobble.val;

        if (diff < 2 * PI * Config.global.theme.zoom_range)
            max_scale = (Config.global.theme.max_zoom/(diff * (Config.global.theme.max_zoom - 1)
                        /(2 * PI * Config.global.theme.zoom_range) + 1))
                        /Config.global.theme.max_zoom;



        max_scale = (slice_track != 0 ? max_scale : 1.0/Config.global.theme.max_zoom);

        if (fabs(this.scale.end - max_scale) > Config.global.theme.max_zoom*0.005)
            this.scale.reset_target(max_scale, Config.global.theme.transition_time);

        ctx.save();

        // distance from the center
        double radius = Config.global.theme.radius;

        // increase radius if there are many slices in a pie
        if (atan((Config.global.theme.slice_radius+Config.global.theme.slice_gap)
          /(radius/Config.global.theme.max_zoom)) > PI/parent.total_slice_count) {
            radius = (Config.global.theme.slice_radius+Config.global.theme.slice_gap)
                     /tan(PI/parent.total_slice_count)*Config.global.theme.max_zoom;
        }

        // transform the context
        ctx.scale(scale.val*fade_scale.val, scale.val*fade_scale.val);
        ctx.translate(cos(direction)*radius, sin(direction)*radius);

        ctx.push_group();

        ctx.set_operator(Cairo.Operator.ADD);

        // paint the images
        if (fade.val > 0.0) active_icon.paint_on(ctx, this.alpha.val*this.fade.val);
        if (fade.val < 1.0) inactive_icon.paint_on(ctx, this.alpha.val*(1.0 - fade.val));

        if (this.parent.show_hotkeys) {
            ctx.set_operator(Cairo.Operator.ATOP);
            ctx.set_source_rgba(0, 0, 0, 0.5);
            ctx.paint();
        }

        ctx.set_operator(Cairo.Operator.OVER);


        ctx.pop_group_to_source();
        ctx.paint();

        // draw hotkeys if necassary
        if (this.parent.show_hotkeys) {
            this.hotkey.paint_on(ctx, 1.0);
        }

        ctx.restore();
    }
}

}
