/* Copyright 2016 Software Freedom Conservancy Inc.
 *
 * This software is licensed under the GNU LGPL (version 2.1 or later).
 * See the COPYING file in this distribution.
 */

public enum Orientation {
    MIN = 1,
    TOP_LEFT = 1,
    TOP_RIGHT = 2,
    BOTTOM_RIGHT = 3,
    BOTTOM_LEFT = 4,
    LEFT_TOP = 5,
    RIGHT_TOP = 6,
    RIGHT_BOTTOM = 7,
    LEFT_BOTTOM = 8,
    MAX = 8;

    public string to_string() {
        switch (this) {
            case TOP_LEFT:
                return "top-left";
                
            case TOP_RIGHT:
                return "top-right";
                
            case BOTTOM_RIGHT:
                return "bottom-right";
                
            case BOTTOM_LEFT:
                return "bottom-left";
                
            case LEFT_TOP:
                return "left-top";
                
            case RIGHT_TOP:
                return "right-top";
                
            case RIGHT_BOTTOM:
                return "right-bottom";
                
            case LEFT_BOTTOM:
                return "left-bottom";
                
            default:
                return "unknown orientation %d".printf((int) this);
        }
    }
    
    public Orientation rotate_clockwise() {
        switch (this) {
            case TOP_LEFT:
                return RIGHT_TOP;
                
            case TOP_RIGHT:
                return RIGHT_BOTTOM;
                
            case BOTTOM_RIGHT:
                return LEFT_BOTTOM;
                
            case BOTTOM_LEFT:
                return LEFT_TOP;
                
            case LEFT_TOP:
                return TOP_RIGHT;
                
            case RIGHT_TOP:
                return BOTTOM_RIGHT;
                
            case RIGHT_BOTTOM:
                return BOTTOM_LEFT;
                
            case LEFT_BOTTOM:
                return TOP_LEFT;
                
            default:
                error("rotate_clockwise: %d", this);
        }
    }
    
    public Orientation rotate_counterclockwise() {
        switch (this) {
            case TOP_LEFT:
                return LEFT_BOTTOM;
                
            case TOP_RIGHT:
                return LEFT_TOP;
                
            case BOTTOM_RIGHT:
                return RIGHT_TOP;
                
            case BOTTOM_LEFT:
                return RIGHT_BOTTOM;
                
            case LEFT_TOP:
                return BOTTOM_LEFT;
                
            case RIGHT_TOP:
                return TOP_LEFT;
                
            case RIGHT_BOTTOM:
                return TOP_RIGHT;
                
            case LEFT_BOTTOM:
                return BOTTOM_RIGHT;
                
            default:
                error("rotate_counterclockwise: %d", this);
        }
    }
    
    public Orientation flip_top_to_bottom() {
        switch (this) {
            case TOP_LEFT:
                return BOTTOM_LEFT;
                
            case TOP_RIGHT:
                return BOTTOM_RIGHT;
                
            case BOTTOM_RIGHT:
                return TOP_RIGHT;
                
            case BOTTOM_LEFT:
                return TOP_LEFT;
                
            case LEFT_TOP:
                return LEFT_BOTTOM;
                
            case RIGHT_TOP:
                return RIGHT_BOTTOM;
                
            case RIGHT_BOTTOM:
                return RIGHT_TOP;
                
            case LEFT_BOTTOM:
                return LEFT_TOP;
                
            default:
                error("flip_top_to_bottom: %d", this);
        }
    }
    
    public Orientation flip_left_to_right() {
        switch (this) {
            case TOP_LEFT:
                return TOP_RIGHT;
                
            case TOP_RIGHT:
                return TOP_LEFT;
                
            case BOTTOM_RIGHT:
                return BOTTOM_LEFT;
                
            case BOTTOM_LEFT:
                return BOTTOM_RIGHT;
                
            case LEFT_TOP:
                return RIGHT_TOP;
                
            case RIGHT_TOP:
                return LEFT_TOP;
                
            case RIGHT_BOTTOM:
                return LEFT_BOTTOM;
                
            case LEFT_BOTTOM:
                return RIGHT_BOTTOM;
                
            default:
                error("flip_left_to_right: %d", this);
        }
    }
    
    public Orientation perform(Rotation rotation) {
        switch (rotation) {
            case Rotation.CLOCKWISE:
                return rotate_clockwise();
            
            case Rotation.COUNTERCLOCKWISE:
                return rotate_counterclockwise();
            
            case Rotation.MIRROR:
                return flip_left_to_right();
            
            case Rotation.UPSIDE_DOWN:
                return flip_top_to_bottom();
            
            default:
                error("perform: %d", (int) rotation);
        }
    }
    
    public Rotation[] to_rotations() {
        switch (this) {
            case TOP_LEFT:
                // identity orientation
                return { };
            
            case TOP_RIGHT:
                return { Rotation.MIRROR };
            
            case BOTTOM_RIGHT:
                return { Rotation.UPSIDE_DOWN };
            
            case BOTTOM_LEFT:
                // flip top-to-bottom
                return { Rotation.MIRROR, Rotation.UPSIDE_DOWN };
            
            case LEFT_TOP:
                return { Rotation.COUNTERCLOCKWISE, Rotation.UPSIDE_DOWN };
            
            case RIGHT_TOP:
                return { Rotation.CLOCKWISE };
            
            case RIGHT_BOTTOM:
                return { Rotation.CLOCKWISE, Rotation.UPSIDE_DOWN };
            
            case LEFT_BOTTOM:
                return { Rotation.COUNTERCLOCKWISE };
            
            default:
                error("to_rotations: %d", this);
        }
    }
    
    public Dimensions rotate_dimensions(Dimensions dim) {
        switch (this) {
            case Orientation.TOP_LEFT:
            case Orientation.TOP_RIGHT:
            case Orientation.BOTTOM_RIGHT:
            case Orientation.BOTTOM_LEFT:
                // fine just as it is
                return dim;

            case Orientation.LEFT_TOP:
            case Orientation.RIGHT_TOP:
            case Orientation.RIGHT_BOTTOM:
            case Orientation.LEFT_BOTTOM:
                // swap
                return Dimensions(dim.height, dim.width);

            default:
                error("rotate_dimensions: %d", this);
        }
    }
    
    public Dimensions derotate_dimensions(Dimensions dim) {
        return rotate_dimensions(dim);
    }

    public Gdk.Pixbuf rotate_pixbuf(Gdk.Pixbuf pixbuf) {
        Gdk.Pixbuf rotated;
        switch (this) {
            case TOP_LEFT:
                // fine just as it is
                rotated = pixbuf;
            break;
            
            case TOP_RIGHT:
                // mirror
                rotated = pixbuf.flip(true);
            break;
            
            case BOTTOM_RIGHT:
                rotated = pixbuf.rotate_simple(Gdk.PixbufRotation.UPSIDEDOWN);
            break;
            
            case BOTTOM_LEFT:
                // flip top-to-bottom
                rotated = pixbuf.flip(false);
            break;
            
            case LEFT_TOP:
                rotated = pixbuf.rotate_simple(Gdk.PixbufRotation.COUNTERCLOCKWISE).flip(false);
            break;
            
            case RIGHT_TOP:
                rotated = pixbuf.rotate_simple(Gdk.PixbufRotation.CLOCKWISE);
            break;
            
            case RIGHT_BOTTOM:
                rotated = pixbuf.rotate_simple(Gdk.PixbufRotation.CLOCKWISE).flip(false);
            break;
            
            case LEFT_BOTTOM:
                rotated = pixbuf.rotate_simple(Gdk.PixbufRotation.COUNTERCLOCKWISE);
            break;
            
            default:
                error("rotate_pixbuf: %d", this);
        }
        
        return rotated;
    }
    
    // space is the unrotated dimensions the point is rotating with
    public Gdk.Point rotate_point(Dimensions space, Gdk.Point point) {
        assert(space.has_area());
        assert(point.x >= 0);
        assert(point.x < space.width);
        assert(point.y >= 0);
        assert(point.y < space.height);
        
        Gdk.Point rotated = Gdk.Point();
        
        switch (this) {
            case TOP_LEFT:
                // fine as-is
                rotated = point;
            break;
                
            case TOP_RIGHT:
                // mirror
                rotated.x = space.width - point.x - 1;
                rotated.y = point.y;
            break;
                
            case BOTTOM_RIGHT:
                // rotate 180
                rotated.x = space.width - point.x - 1;
                rotated.y = space.height - point.y - 1;
            break;
                
            case BOTTOM_LEFT:
                // flip top-to-bottom
                rotated.x = point.x;
                rotated.y = space.height - point.y - 1;
            break;
                
            case LEFT_TOP:
                // rotate 90, flip top-to-bottom
                rotated.x = point.y;
                rotated.y = point.x;
            break;
                
            case RIGHT_TOP:
                // rotate 270
                rotated.x = space.height - point.y - 1;
                rotated.y = point.x;
            break;
                
            case RIGHT_BOTTOM:
                // rotate 270, flip top-to-bottom
                rotated.x = space.height - point.y - 1;
                rotated.y = space.width - point.x - 1;
            break;
                
            case LEFT_BOTTOM:
                // rotate 90
                rotated.x = point.y;
                rotated.y = space.width - point.x - 1;
            break;
                
            default:
                error("rotate_point: %d", this);
        }
        
        return rotated;
    }
    
    // space is the unrotated dimensions the point is return to
    public Gdk.Point derotate_point(Dimensions space, Gdk.Point point) {
        assert(space.has_area());
        
        Gdk.Point derotated = Gdk.Point();
        
        switch (this) {
            case TOP_LEFT:
                // fine as-is
                derotated = point;
            break;
                
            case TOP_RIGHT:
                // mirror
                derotated.x = space.width - point.x - 1;
                derotated.y = point.y;
            break;
                
            case BOTTOM_RIGHT:
                // rotate 180
                derotated.x = space.width - point.x - 1;
                derotated.y = space.height - point.y - 1;
            break;
                
            case BOTTOM_LEFT:
                // flip top-to-bottom
                derotated.x = point.x;
                derotated.y = space.height - point.y - 1;
            break;
                
            case LEFT_TOP:
                // rotate 90, flip top-to-bottom
                derotated.x = point.y;
                derotated.y = point.x;
            break;
                
            case RIGHT_TOP:
                // rotate 270
                derotated.x = point.y;
                derotated.y = space.height - point.x - 1;
            break;
                
            case RIGHT_BOTTOM:
                // rotate 270, flip top-to-bottom
                derotated.x = space.width - point.y - 1;
                derotated.y = space.height - point.x - 1;
            break;
                
            case LEFT_BOTTOM:
                // rotate 90
                derotated.x = space.width - point.y - 1;
                derotated.y = point.x;
            break;
                
            default:
                error("rotate_point: %d", this);
        }
        
        return derotated;
    }
    
    // space is the unrotated dimensions the point is rotating with
    public Box rotate_box(Dimensions space, Box box) {
        Gdk.Point top_left, bottom_right;
        box.get_points(out top_left, out bottom_right);
        
        top_left.x = top_left.x.clamp(0, space.width - 1);
        top_left.y = top_left.y.clamp(0, space.height - 1);

        bottom_right.x = bottom_right.x.clamp(0, space.width - 1);
        bottom_right.y = bottom_right.y.clamp(0, space.height - 1);
        
        top_left = rotate_point(space, top_left);
        bottom_right = rotate_point(space, bottom_right);
        
        return Box.from_points(top_left, bottom_right);
    }
    
    // space is the unrotated dimensions the point is return to
    public Box derotate_box(Dimensions space, Box box) {
        Gdk.Point top_left, bottom_right;
        box.get_points(out top_left, out bottom_right);
        
        top_left = derotate_point(space, top_left);
        bottom_right = derotate_point(space, bottom_right);
        
        return Box.from_points(top_left, bottom_right);
    }
}

public enum Rotation {
    CLOCKWISE,
    COUNTERCLOCKWISE,
    MIRROR,
    UPSIDE_DOWN;
    
    public Gdk.Pixbuf perform(Gdk.Pixbuf pixbuf) {
        switch (this) {
            case CLOCKWISE:
                return pixbuf.rotate_simple(Gdk.PixbufRotation.CLOCKWISE);
            
            case COUNTERCLOCKWISE:
                return pixbuf.rotate_simple(Gdk.PixbufRotation.COUNTERCLOCKWISE);
            
            case MIRROR:
                return pixbuf.flip(true);
            
            case UPSIDE_DOWN:
                return pixbuf.flip(false);
            
            default:
                error("Unknown rotation: %d", (int) this);
        }
    }
    
    public Rotation opposite() {
        switch (this) {
            case CLOCKWISE:
                return COUNTERCLOCKWISE;
            
            case COUNTERCLOCKWISE:
                return CLOCKWISE;
            
            case MIRROR:
            case UPSIDE_DOWN:
                return this;
            
            default:
                error("Unknown rotation: %d", (int) this);
        }
    }
}