/*
 * Copyright (C) 2009-2015 Canonical Ltd.
 * Author: Robert Ancell <robert.ancell@canonical.com>
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
 * license.
 */

public enum CropLocation
{
    NONE = 0,
    MIDDLE,
    TOP,
    BOTTOM,
    LEFT,
    RIGHT,
    TOP_LEFT,
    TOP_RIGHT,
    BOTTOM_LEFT,
    BOTTOM_RIGHT
}

public class PageView : Object
{
    /* Page being rendered */
    public Page page { get; private set; }

    /* Image to render at current resolution */
    private PageViewTexture page_texture;

    /* Border around image */
    private bool selected_ = false;
    public bool selected
    {
        get { return selected_; }
        set
        {
            if ((this.selected && selected) || (!this.selected && !selected))
                return;
            this.selected = selected;
            changed ();
        }
    }

    private int ruler_width = 8;

    private int border_width = 2;

    /* True if image needs to be regenerated */
    private bool update_image = true;

    /* Dimensions of image to generate */
    private int width_;
    private int height_;

    /* Location to place this page */
    public int x_offset { get; set; }
    public int y_offset { get; set; }

    private CropLocation crop_location;
    private double selected_crop_px;
    private double selected_crop_py;
    private int selected_crop_x;
    private int selected_crop_y;
    private int selected_crop_w;
    private int selected_crop_h;

    /* Cursor over this page */
    public string cursor { get; private set; default = "default"; }

    private int animate_n_segments = 7;
    private int animate_segment;
    private uint animate_timeout;

    public signal void size_changed ();
    public signal void changed ();

    public PageView (Page page)
    {
        this.page = page;
        page.pixels_changed.connect (page_pixels_changed_cb);
        page.size_changed.connect (page_size_changed_cb);
        page.crop_changed.connect (page_overlay_changed_cb);
        page.scan_line_changed.connect (page_overlay_changed_cb);
        page.scan_direction_changed.connect (scan_direction_changed_cb);
        
        page_texture = new PageViewTexture(page);
        page_texture.new_buffer.connect (new_buffer_cb);
    }

    ~PageView ()
    {
        page.pixels_changed.disconnect (page_pixels_changed_cb);
        page.size_changed.disconnect (page_size_changed_cb);
        page.crop_changed.disconnect (page_overlay_changed_cb);
        page.scan_line_changed.disconnect (page_overlay_changed_cb);
        page.scan_direction_changed.disconnect (scan_direction_changed_cb);

        page_texture.new_buffer.disconnect (new_buffer_cb);
    }
    
    private void new_buffer_cb()
    {
        changed ();
    }

    private int get_preview_width ()
    {
        return width_ - (border_width + ruler_width) * 2;
    }

    private int get_preview_height ()
    {
        return height_ - (border_width + ruler_width) * 2;
    }

    private int page_to_screen_x (int x)
    {
        return (int) ((double)x * get_preview_width () / page.width + 0.5);
    }

    private int page_to_screen_y (int y)
    {
        return (int) ((double)y * get_preview_height () / page.height + 0.5);
    }

    private int screen_to_page_x (int x)
    {
        return (int) ((double)x * page.width / get_preview_width () + 0.5);
    }

    private int screen_to_page_y (int y)
    {
        return (int) ((double)y * page.height / get_preview_height () + 0.5);
    }

    private CropLocation get_crop_location (int x, int y)
    {
        if (!page.has_crop)
            return CropLocation.NONE;

        var cx = page.crop_x;
        var cy = page.crop_y;
        var cw = page.crop_width;
        var ch = page.crop_height;
        var dx = page_to_screen_x (cx) + border_width + ruler_width;
        var dy = page_to_screen_y (cy) + border_width + ruler_width;
        var dw = page_to_screen_x (cw) + border_width + ruler_width;
        var dh = page_to_screen_y (ch) + border_width + ruler_width;
        var ix = x - dx;
        var iy = y - dy;

        if (ix < 0 || ix > dw || iy < 0 || iy > dh)
            return CropLocation.NONE;

        /* Can't resize named crops */
        var name = page.crop_name;
        if (name != null)
            return CropLocation.MIDDLE;

        /* Adjust borders so can select */
        int crop_border = 20;
        if (dw < crop_border * 3)
            crop_border = dw / 3;
        if (dh < crop_border * 3)
            crop_border = dh / 3;

        /* Top left */
        if (ix < crop_border && iy < crop_border)
            return CropLocation.TOP_LEFT;
        /* Top right */
        if (ix > dw - crop_border && iy < crop_border)
            return CropLocation.TOP_RIGHT;
        /* Bottom left */
        if (ix < crop_border && iy > dh - crop_border)
            return CropLocation.BOTTOM_LEFT;
        /* Bottom right */
        if (ix > dw - crop_border && iy > dh - crop_border)
            return CropLocation.BOTTOM_RIGHT;

        /* Left */
        if (ix < crop_border)
            return CropLocation.LEFT;
        /* Right */
        if (ix > dw - crop_border)
            return CropLocation.RIGHT;
        /* Top */
        if (iy < crop_border)
            return CropLocation.TOP;
        /* Bottom */
        if (iy > dh - crop_border)
            return CropLocation.BOTTOM;

        /* In the middle */
        return CropLocation.MIDDLE;
    }

    public void button_press (int x, int y)
    {
        /* See if selecting crop */
        var location = get_crop_location (x, y);
        if (location != CropLocation.NONE)
        {
            crop_location = location;
            selected_crop_px = x;
            selected_crop_py = y;
            selected_crop_x = page.crop_x;
            selected_crop_y = page.crop_y;
            selected_crop_w = page.crop_width;
            selected_crop_h = page.crop_height;
        }
    }

    public void motion (int x, int y)
    {
        var location = get_crop_location (x, y);

        string cursor;
        switch (location)
        {
        case CropLocation.MIDDLE:
            cursor = "all-resize";
            break;
        case CropLocation.TOP:
            cursor = "n-resize";
            break;
        case CropLocation.BOTTOM:
            cursor = "s-resize";
            break;
        case CropLocation.LEFT:
            cursor = "w-resize";
            break;
        case CropLocation.RIGHT:
            cursor = "e-resize";
            break;
        case CropLocation.TOP_LEFT:
            cursor = "nw-resize";
            break;
        case CropLocation.TOP_RIGHT:
            cursor = "ne-resize";
            break;
        case CropLocation.BOTTOM_LEFT:
            cursor = "sw-resize";
            break;
        case CropLocation.BOTTOM_RIGHT:
            cursor = "se-resize";
            break;
        default:
            cursor = "default";
            break;
        }

        if (crop_location == CropLocation.NONE)
        {
            this.cursor = cursor;
            return;
        }

        /* Move the crop */
        var pw = page.width;
        var ph = page.height;
        var cw = page.crop_width;
        var ch = page.crop_height;

        var dx = screen_to_page_x (x - (int) selected_crop_px);
        var dy = screen_to_page_y (y - (int) selected_crop_py);

        var new_x = selected_crop_x;
        var new_y = selected_crop_y;
        var new_w = selected_crop_w;
        var new_h = selected_crop_h;

        /* Limit motion to remain within page and minimum crop size */
        var min_size = screen_to_page_x (15);
        if (crop_location == CropLocation.TOP_LEFT ||
            crop_location == CropLocation.LEFT ||
            crop_location == CropLocation.BOTTOM_LEFT)
        {
            if (dx > new_w - min_size)
                dx = new_w - min_size;
            if (new_x + dx < 0)
                dx = -new_x;
        }
        if (crop_location == CropLocation.TOP_LEFT ||
            crop_location == CropLocation.TOP ||
            crop_location == CropLocation.TOP_RIGHT)
        {
            if (dy > new_h - min_size)
                dy = new_h - min_size;
            if (new_y + dy < 0)
                dy = -new_y;
        }

        if (crop_location == CropLocation.TOP_RIGHT ||
            crop_location == CropLocation.RIGHT ||
            crop_location == CropLocation.BOTTOM_RIGHT)
        {
            if (dx < min_size - new_w)
                dx = min_size - new_w;
            if (new_x + new_w + dx > pw)
                dx = pw - new_x - new_w;
        }
        if (crop_location == CropLocation.BOTTOM_LEFT ||
            crop_location == CropLocation.BOTTOM ||
            crop_location == CropLocation.BOTTOM_RIGHT)
        {
            if (dy < min_size - new_h)
                dy = min_size - new_h;
            if (new_y + new_h + dy > ph)
                dy = ph - new_y - new_h;
        }
        if (crop_location == CropLocation.MIDDLE)
        {
            if (new_x + dx + new_w > pw)
                dx = pw - new_x - new_w;
            if (new_x + dx < 0)
                dx = -new_x;
            if (new_y + dy + new_h > ph)
                dy = ph - new_y - new_h;
            if (new_y + dy  < 0)
                dy = -new_y;
        }

        /* Move crop */
        if (crop_location == CropLocation.MIDDLE)
        {
            new_x += dx;
            new_y += dy;
        }
        if (crop_location == CropLocation.TOP_LEFT ||
            crop_location == CropLocation.LEFT ||
            crop_location == CropLocation.BOTTOM_LEFT)
        {
            new_x += dx;
            new_w -= dx;
        }
        if (crop_location == CropLocation.TOP_LEFT ||
            crop_location == CropLocation.TOP ||
            crop_location == CropLocation.TOP_RIGHT)
        {
            new_y += dy;
            new_h -= dy;
        }

        if (crop_location == CropLocation.TOP_RIGHT ||
            crop_location == CropLocation.RIGHT ||
            crop_location == CropLocation.BOTTOM_RIGHT)
            new_w += dx;
        if (crop_location == CropLocation.BOTTOM_LEFT ||
            crop_location == CropLocation.BOTTOM ||
            crop_location == CropLocation.BOTTOM_RIGHT)
            new_h += dy;

        page.move_crop (new_x, new_y);

        /* If reshaped crop, must be a custom crop */
        if (new_w != cw || new_h != ch)
            page.set_custom_crop (new_w, new_h);
    }

    public void button_release (int x, int y)
    {
        /* Complete crop */
        crop_location = CropLocation.NONE;
        changed ();
    }

    private bool animation_cb ()
    {
        animate_segment = (animate_segment + 1) % animate_n_segments;
        changed ();
        return true;
    }

    private void update_animation ()
    {
        bool animate, is_animating;

        animate = page.is_scanning && !page.has_data;
        is_animating = animate_timeout != 0;
        if (animate == is_animating)
            return;

        if (animate)
        {
            animate_segment = 0;
            if (animate_timeout == 0)
                animate_timeout = Timeout.add (150, animation_cb);
        }
        else
        {
            if (animate_timeout != 0)
                Source.remove (animate_timeout);
            animate_timeout = 0;
        }
    }

    /* It is necessary to ask the ruler color since it is themed with the GTK */
    /* theme foreground color, and this class doesn't have any GTK widget     */
    /* available to lookup the color. */
    public void render (Cairo.Context context, Gdk.RGBA ruler_color)
    {
        update_animation ();

        page_texture.request_resize (get_preview_width (), get_preview_height ());
        
        try {
            page_texture.queue_update ();
        }
        catch (Error e)
        {
            warning ("Failed to queue_update of the texture: %s", e.message);
            // Ask for another redraw
            changed ();
        }

        var w = get_preview_width ();
        var h = get_preview_height ();

        context.set_line_width (1);
        context.translate (x_offset, y_offset);

        /* Draw image */
        context.translate (border_width + ruler_width, border_width + ruler_width);

        if (page_texture.pixbuf != null)
        {
            float x_scale = (float) w / (float) page_texture.pixbuf.width;
            float y_scale = (float) h / (float) page_texture.pixbuf.height;
            
            context.save ();
            context.scale(x_scale, y_scale);

            //  context.rectangle (0, 0.0, w, h);
            Gdk.cairo_set_source_pixbuf (context, page_texture.pixbuf, 0, 0);
            context.paint ();
            context.restore ();
        }
        else
        {
            Gdk.cairo_set_source_rgba (context, {1.0f, 1.0f, 1.0f, 1.0f});
            context.rectangle (0, 0.0, w, h);
            context.fill ();
        }

        /* Draw page border */
        Gdk.cairo_set_source_rgba (context, ruler_color);
        context.set_line_width (border_width);

        context.rectangle (0,
                           0.0,
                           w,
                           h);
        context.stroke ();

        /* Draw horizontal ruler */
        context.set_line_width (1);
        var ruler_tick = 0;
        var line = 0.0;
        var big_ruler_tick = 5;

        while (ruler_tick <= page.width)
        {
            line = page_to_screen_x (ruler_tick) + 0.5;
            if (big_ruler_tick == 5)
            {
                context.move_to (line, 0);
                context.line_to (line, -ruler_width);
                context.move_to (line, h);
                context.line_to (line, h + ruler_width);
                big_ruler_tick = 0;
            }
            else
            {
                context.move_to (line, -2);
                context.line_to (line, -5);
                context.move_to (line, h + 2);
                context.line_to (line, h + 5);
            }
            ruler_tick = ruler_tick + page.dpi/5;
            big_ruler_tick = big_ruler_tick + 1;
        }
        context.stroke ();

        /* Draw vertical ruler */
        ruler_tick = 0;
        line = 0.0;
        big_ruler_tick = 5;
        while (ruler_tick <= page.height)
        {
            line = page_to_screen_y (ruler_tick) + 0.5;

            if (big_ruler_tick == 5)
            {
                context.move_to (0, line);
                context.line_to (-ruler_width, line);

                context.move_to (w, line);
                context.line_to (w + ruler_width, line);
                big_ruler_tick = 0;
            }
            else
            {
                context.move_to (-2, line);
                context.line_to (-5, line);

                context.move_to (w + 2, line);
                context.line_to (w + 5, line);
            }
            ruler_tick = ruler_tick + page.dpi/5;
            big_ruler_tick = big_ruler_tick + 1;
        }
        context.stroke ();

        /* Draw scan line */
        if (page.is_scanning && page.scan_line > 0)
        {
            var scan_line = page.scan_line;

            double s;
            double x1, y1, x2, y2;
            switch (page.scan_direction)
            {
            case ScanDirection.TOP_TO_BOTTOM:
                s = page_to_screen_y (scan_line);
                x1 = 0; y1 = s + 0.5;
                x2 = w; y2 = s + 0.5;
                break;
            case ScanDirection.BOTTOM_TO_TOP:
                s = page_to_screen_y (scan_line);
                x1 = 0; y1 = h - s + 0.5;
                x2 = w; y2 = h - s + 0.5;
                break;
            case ScanDirection.LEFT_TO_RIGHT:
                s = page_to_screen_x (scan_line);
                x1 = s + 0.5; y1 = 0;
                x2 = s + 0.5; y2 = h;
                break;
            case ScanDirection.RIGHT_TO_LEFT:
                s = page_to_screen_x (scan_line);
                x1 = w - s + 0.5; y1 = 0;
                x2 = w - s + 0.5; y2 = h;
                break;
            default:
                x1 = y1 = x2 = y2 = 0;
                break;
            }

            context.move_to (x1, y1);
            context.line_to (x2, y2);
            context.set_source_rgb (1.0, 0.0, 0.0);
            context.stroke ();
        }

        /* Draw crop */
        if (page.has_crop)
        {
            var x = page.crop_x;
            var y = page.crop_y;
            var crop_width = page.crop_width;
            var crop_height = page.crop_height;

            var dx = page_to_screen_x (x);
            var dy = page_to_screen_y (y);
            var dw = page_to_screen_x (crop_width);
            var dh = page_to_screen_y (crop_height);

            /* Shade out cropped area */
            context.rectangle (0, 0, w, h);
            context.new_sub_path ();
            context.rectangle (dx, dy, dw, dh);
            context.set_fill_rule (Cairo.FillRule.EVEN_ODD);
            context.set_source_rgba (0.25, 0.25, 0.25, 0.2);
            context.fill ();

            /* Show new edge */
            context.set_source_rgb (1.0, 1.0, 1.0);
            context.move_to (-border_width, dy - 1.5);
            context.line_to (border_width + w, dy - 1.5);
            context.move_to (-border_width, dy + dh + 1.5);
            context.line_to (border_width + w, dy + dh + 1.5);
            context.stroke ();

            context.move_to (dx - 1.5, -border_width);
            context.line_to (dx - 1.5, border_width + h);
            context.move_to (dx + dw + 1.5, -border_width);
            context.line_to (dx + dw + 1.5, border_width + h);
            context.stroke ();

            context.rectangle (dx - 0.5, dy - 0.5, dw + 1, dh + 1);
            context.set_source_rgb (0.0, 0.0, 0.0);
            context.stroke ();
        }
    }

    public int width
    {
        get { return width_; }
        set
        {
            // FIXME: Automatically update when get updated image
            var h = (int) ((double) value * page.height / page.width);
            if (width_ == value && height_ == h)
                return;

            width_ = value;
            height_ = h;

            /* Regenerate image */
            update_image = true;

            size_changed ();
            changed ();
        }
    }

    public int height
    {
        get { return height_; }
        set
        {
            // FIXME: Automatically update when get updated image
            var w = (int) ((double) value * page.width / page.height);
            if (width_ == w && height_ == value)
                return;

            width_ = w;
            height_ = value;

            /* Regenerate image */
            update_image = true;

            size_changed ();
            changed ();
        }
    }

    private void page_pixels_changed_cb (Page p)
    {
        /* Regenerate image */
        update_image = true;
        page_texture.request_update ();
        changed ();
    }

    private void page_size_changed_cb (Page p)
    {
        /* Regenerate image */
        update_image = true;
        size_changed ();
        changed ();
    }

    private void page_overlay_changed_cb (Page p)
    {
        changed ();
    }

    private void scan_direction_changed_cb (Page p)
    {
        /* Regenerate image */
        update_image = true;
        page_texture.request_update ();
        size_changed ();
        changed ();
    }
}
