/// <reference path="./ModalPermissionEdit.ts" /> /* first needs the AbstractPermissionEdit */

/* Canvas Permission Editor */
namespace pe {
    namespace ui {
        export namespace scheme {
            export interface CheckBox {
                border: string;
                checkmark: string;
                checkmark_font: string;

                background_checked: string;
                background_checked_hovered: string;

                background: string;
                background_hovered: string;
            }

            export interface TextField {
                color: string;
                font: string;

                background: string;
                background_hovered: string;
            }

            export interface ColorScheme {
                permission: {
                    background: string;
                    background_selected: string;

                    name: string;
                    name_unset: string;
                    name_font: string;

                    value: TextField;
                    value_b: CheckBox;
                    granted: TextField;
                    negate: CheckBox;
                    skip: CheckBox;
                }

                group: {
                    name: string;
                    name_font: string;
                }
            }
        }

        export enum RepaintMode {
            NONE,
            REPAINT,
            REPAINT_OBJECT_FULL,
            REPAINT_FULL
        }

        export interface AxisAlignedBoundingBox {
            x: number;
            y: number;

            width: number;
            height: number;
        }

        export enum ClickEventType {
            SIGNLE,
            DOUBLE,
            CONTEXT_MENU
        }

        export interface InteractionClickEvent {
            type: ClickEventType;
            consumed: boolean;
            offset_x: number;
            offset_y: number;
        }

        export interface InteractionListener {
            region: AxisAlignedBoundingBox;
            region_weight: number;

            /**
             * @return true if a redraw is required
             */
            on_mouse_enter?: () => RepaintMode;

            /**
             * @return true if a redraw is required
             */
            on_mouse_leave?: () => RepaintMode;

            /**
             * @return true if a redraw is required
             */
            on_click?: (event: InteractionClickEvent) => RepaintMode;

            mouse_cursor?: string;

            set_full_draw?: () => any;
            disabled?: boolean;
        }

        abstract class DrawableObject {
            abstract draw(context: CanvasRenderingContext2D, full: boolean);

            private _object_full_draw = false;
            private _width: number = 0;

            set_width(value: number) {
                this._width = value;
            }

            request_full_draw() {
                this._object_full_draw = true;
            }

            pop_full_draw() {
                const result = this._object_full_draw;
                this._object_full_draw = false;
                return result;
            }

            width() {
                return this._width;
            }

            abstract height();

            private _transforms: DOMMatrix[] = [];

            protected push_transform(context: CanvasRenderingContext2D) {
                this._transforms.push(context.getTransform());
            }

            protected pop_transform(context: CanvasRenderingContext2D) {
                const transform = this._transforms.pop();
                context.setTransform(
                    transform.a,
                    transform.b,
                    transform.c,
                    transform.d,
                    transform.e,
                    transform.f
                );
            }

            protected original_x(context: CanvasRenderingContext2D, x: number) {
                return context.getTransform().e + x;
            }

            protected original_y(context: CanvasRenderingContext2D, y: number) {
                return context.getTransform().f + y;
            }

            protected colors: scheme.ColorScheme = {} as any;

            set_color_scheme(scheme: scheme.ColorScheme) {
                this.colors = scheme;
            }

            protected manager: PermissionEditor;

            set_manager(manager: PermissionEditor) {
                this.manager = manager;
            }

            abstract initialize();

            abstract finalize();
        }

        class PermissionGroup extends DrawableObject {
            public static readonly HEIGHT = parseFloat(getComputedStyle(document.documentElement).fontSize) * (3 / 2); /* 24 */
            public static readonly ARROW_SIZE = 10; /* 12 */

            group: GroupedPermissions;
            _sub_elements: PermissionGroup[] = [];
            _element_permissions: PermissionList;

            collapsed = false;
            private _listener_colaps: InteractionListener;

            constructor(group: GroupedPermissions) {
                super();

                this.group = group;

                this._element_permissions = new PermissionList(this.group.permissions);
                for (const sub of this.group.children)
                    this._sub_elements.push(new PermissionGroup(sub));
            }

            draw(context: CanvasRenderingContext2D, full: boolean) {
                const _full = this.pop_full_draw() || full;
                this.push_transform(context);
                context.translate(PermissionGroup.ARROW_SIZE + 20, PermissionGroup.HEIGHT);

                let sum_height = 0;
                /* let first draw the elements, because if the sum height is zero then we could hide ourselves */
                if (!this.collapsed) { /* draw the next groups */
                    for (const group of this._sub_elements) {
                        group.draw(context, full);

                        const height = group.height();
                        sum_height += height;
                        context.translate(0, height);
                    }

                    this._element_permissions.draw(context, full);
                    if (sum_height == 0)
                        sum_height += this._element_permissions.height();
                } else {
                    const process_group = (group: PermissionGroup) => {
                        for (const g of group._sub_elements)
                            process_group(g);
                        group._element_permissions.handle_hide();
                        if (sum_height == 0 && group._element_permissions.height() > 0) {
                            sum_height = 1;
                        }
                    };
                    process_group(this);
                }
                this.pop_transform(context);

                if (_full && sum_height > 0) {
                    const arrow_stretch = 2 / 3;
                    if (!full) {
                        context.clearRect(0, 0, this.width(), PermissionGroup.HEIGHT);
                    }
                    context.fillStyle = this.colors.group.name;

                    /* arrow */
                    {
                        const x1 = this.collapsed ? PermissionGroup.ARROW_SIZE * arrow_stretch / 2 : 0;
                        const y1 = (PermissionGroup.HEIGHT - PermissionGroup.ARROW_SIZE) / 2 + (this.collapsed ? 0 : PermissionGroup.ARROW_SIZE * arrow_stretch / 2);  /* center arrow  */

                        const x2 = this.collapsed ? x1 + PermissionGroup.ARROW_SIZE * arrow_stretch : x1 + PermissionGroup.ARROW_SIZE / 2;
                        const y2 = this.collapsed ? y1 + PermissionGroup.ARROW_SIZE / 2 : y1 + PermissionGroup.ARROW_SIZE * arrow_stretch;

                        const x3 = this.collapsed ? x1 : x1 + PermissionGroup.ARROW_SIZE;
                        const y3 = this.collapsed ? y1 + PermissionGroup.ARROW_SIZE : y1;

                        context.beginPath();
                        context.moveTo(x1, y1);

                        context.lineTo(x2, y2);
                        context.lineTo(x3, y3);

                        context.moveTo(x2, y2);
                        context.lineTo(x3, y3);
                        context.fill();

                        this._listener_colaps.region.x = this.original_x(context, 0);
                        this._listener_colaps.region.y = this.original_y(context, y1);
                    }
                    /* text */
                    {
                        context.font = this.colors.group.name_font;
                        context.textBaseline = "middle";
                        context.textAlign = "start";

                        context.fillText(this.group.group.name, PermissionGroup.ARROW_SIZE + 5, PermissionGroup.HEIGHT / 2);
                    }
                }
            }

            set_width(value: number) {
                super.set_width(value);
                for (const element of this._sub_elements)
                    element.set_width(value - PermissionGroup.ARROW_SIZE - 20);
                this._element_permissions.set_width(value - PermissionGroup.ARROW_SIZE - 20);
            }

            set_color_scheme(scheme: scheme.ColorScheme) {
                super.set_color_scheme(scheme);
                for (const child of this._sub_elements)
                    child.set_color_scheme(scheme);
                this._element_permissions.set_color_scheme(scheme);
            }

            set_manager(manager: PermissionEditor) {
                super.set_manager(manager);
                for (const child of this._sub_elements)
                    child.set_manager(manager);
                this._element_permissions.set_manager(manager);
            }

            height() {
                let result = 0;

                if (!this.collapsed) {
                    for (const element of this._sub_elements)
                        result += element.height();

                    result += this._element_permissions.height();
                } else {
                    //We've to figure out if we have permissions
                    const process_group = (group: PermissionGroup) => {
                        if (result == 0 && group._element_permissions.height() > 0) {
                            result = 1;
                        } else {
                            for (const g of group._sub_elements)
                                process_group(g);
                        }
                    };
                    process_group(this);

                    if (result > 0)
                        return PermissionGroup.HEIGHT;

                    return 0;
                }
                if (result > 0) {
                    result += PermissionGroup.HEIGHT;
                    return result;
                } else {
                    return 0;
                }
            }

            initialize() {
                for (const child of this._sub_elements)
                    child.initialize();
                this._element_permissions.initialize();


                this._listener_colaps = {
                    region: {
                        x: 0,
                        y: 0,
                        height: PermissionGroup.ARROW_SIZE,
                        width: PermissionGroup.ARROW_SIZE
                    },
                    region_weight: 10,
                    /*
                    on_mouse_enter: () => {
                        this.collapsed_hovered = true;
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    on_mouse_leave: () => {
                        this.collapsed_hovered = false;
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    */
                    on_click: () => {
                        this.collapsed = !this.collapsed;
                        return RepaintMode.REPAINT_FULL;
                    },
                    set_full_draw: () => this.request_full_draw(),
                    mouse_cursor: "pointer"
                };

                this.manager.intercept_manager().register_listener(this._listener_colaps);
            }

            finalize() {
                for (const child of this._sub_elements)
                    child.finalize();
                this._element_permissions.finalize();
            }

            collapse_group() {
                for (const child of this._sub_elements)
                    child.collapse_group();

                this.collapsed = true;
            }

            expend_group() {
                for (const child of this._sub_elements)
                    child.expend_group();

                this.collapsed = false;
            }
        }

        class PermissionList extends DrawableObject {
            permissions: PermissionEntry[] = [];

            constructor(permissions: PermissionInfo[]) {
                super();

                for (const permission of permissions)
                    this.permissions.push(new PermissionEntry(permission));
            }

            set_width(value: number) {
                super.set_width(value);
                for (const entry of this.permissions)
                    entry.set_width(value);
            }


            draw(context: CanvasRenderingContext2D, full: boolean) {
                this.push_transform(context);

                for (const permission of this.permissions) {
                    permission.draw(context, full);
                    context.translate(0, permission.height());
                }

                this.pop_transform(context);
            }

            height() {
                let height = 0;
                for (const permission of this.permissions)
                    height += permission.height();
                return height;
            }


            set_color_scheme(scheme: scheme.ColorScheme) {
                super.set_color_scheme(scheme);
                for (const entry of this.permissions)
                    entry.set_color_scheme(scheme);
            }

            set_manager(manager: PermissionEditor) {
                super.set_manager(manager);

                for (const entry of this.permissions)
                    entry.set_manager(manager);
            }

            initialize() {
                for (const entry of this.permissions)
                    entry.initialize();
            }

            finalize() {
                for (const entry of this.permissions)
                    entry.finalize();
            }

            handle_hide() {
                for (const entry of this.permissions)
                    entry.handle_hide();
            }
        }

        class PermissionEntry extends DrawableObject {
            public static readonly HEIGHT = PermissionGroup.HEIGHT; /* 24 */
            public static readonly HALF_HEIGHT = PermissionEntry.HEIGHT / 2;
            public static readonly CHECKBOX_HEIGHT = PermissionEntry.HEIGHT - 2;

            public static readonly COLUMN_PADDING = 2;
            public static readonly COLUMN_VALUE = 75;
            public static readonly COLUMN_GRANTED = 75;
            //public static readonly COLUMN_NEGATE = 25;
            //public static readonly COLUMN_SKIP = 25;
            public static readonly COLUMN_NEGATE = 75;
            public static readonly COLUMN_SKIP = 75;

            private _permission: PermissionInfo;

            hidden: boolean;

            granted: number = 22;
            value: number;
            flag_skip: boolean = true;
            flag_negate: boolean;

            private _prev_selected = false;
            selected: boolean;

            flag_skip_hovered = false;
            flag_negate_hovered = false;
            flag_value_hovered = false;
            flag_grant_hovered = false;

            private _listener_checkbox_skip: InteractionListener;
            private _listener_checkbox_negate: InteractionListener;
            private _listener_value: InteractionListener;
            private _listener_grant: InteractionListener;
            private _listener_general: InteractionListener;
            private _icon_image: HTMLImageElement | undefined;

            on_icon_select?: (current_id: number) => Promise<number>;
            on_context_menu?: (x: number, y: number) => any;
            on_grant_change?: () => any;
            on_change?: () => any;

            constructor(permission: PermissionInfo) {
                super();
                this._permission = permission;
            }

            set_icon_id_image(image: HTMLImageElement | undefined) {
                if (this._icon_image === image)
                    return;
                this._icon_image = image;
                if (image) {
                    image.height = 16;
                    image.width = 16;
                }
            }

            permission() {
                return this._permission;
            }

            draw(ctx: CanvasRenderingContext2D, full: boolean) {
                if (!this.pop_full_draw() && !full) { /* Note: do not change this order! */
                    /* test for update! */
                    return;
                }
                if (this.hidden) {
                    this.handle_hide();
                    return;
                }
                ctx.lineWidth = 1;

                /* debug box */
                if (false) {
                    ctx.fillStyle = "#FF0000";
                    ctx.fillRect(0, 0, this.width(), PermissionEntry.HEIGHT);
                    ctx.fillStyle = "#000000";
                    ctx.strokeRect(0, 0, this.width(), PermissionEntry.HEIGHT);
                }

                if (!full) {
                    const off = this.selected || this._prev_selected ? ctx.getTransform().e : 0;
                    ctx.clearRect(-off, 0, this.width() + off, PermissionEntry.HEIGHT);
                }

                if (this.selected)
                    ctx.fillStyle = this.colors.permission.background_selected;
                else
                    ctx.fillStyle = this.colors.permission.background;
                const off = this.selected ? ctx.getTransform().e : 0;
                ctx.fillRect(-off, 0, this.width() + off, PermissionEntry.HEIGHT);
                this._prev_selected = this.selected;

                /* permission name */
                {
                    ctx.fillStyle = typeof (this.value) !== "undefined" ? this.colors.permission.name : this.colors.permission.name_unset;
                    ctx.textBaseline = "middle";
                    ctx.textAlign = "start";
                    ctx.font = this.colors.permission.name_font;

                    ctx.fillText(this._permission.name, 0, PermissionEntry.HALF_HEIGHT);
                }

                const original_y = this.original_y(ctx, 0);
                const original_x = this.original_x(ctx, 0);
                const width = this.width();

                /* draw granted */
                let w = width - PermissionEntry.COLUMN_GRANTED;
                if (typeof (this.granted) === "number") {
                    this._listener_grant.region.x = original_x + w;
                    this._listener_grant.region.y = original_y;

                    this._draw_number_field(ctx, this.colors.permission.granted, w, 0, PermissionEntry.COLUMN_VALUE, this.granted, this.flag_grant_hovered);
                } else {
                    this._listener_grant.region.y = original_y;
                    this._listener_grant.region.x =
                        original_x
                        + width
                        - PermissionEntry.COLUMN_GRANTED;
                }

                /* draw value and the skip stuff */
                if (typeof (this.value) === "number") {
                    w -= PermissionEntry.COLUMN_SKIP + PermissionEntry.COLUMN_PADDING;
                    {
                        const x = w + (PermissionEntry.COLUMN_SKIP - PermissionEntry.CHECKBOX_HEIGHT) / 2;
                        const y = 1;

                        this._listener_checkbox_skip.region.x = original_x + x;
                        this._listener_checkbox_skip.region.y = original_y + y;

                        this._draw_checkbox_field(ctx, this.colors.permission.skip, x, y, PermissionEntry.CHECKBOX_HEIGHT, this.flag_skip, this.flag_skip_hovered);
                    }

                    w -= PermissionEntry.COLUMN_NEGATE + PermissionEntry.COLUMN_PADDING;
                    {
                        const x = w + (PermissionEntry.COLUMN_NEGATE - PermissionEntry.CHECKBOX_HEIGHT) / 2;
                        const y = 1;

                        this._listener_checkbox_negate.region.x = original_x + x;
                        this._listener_checkbox_negate.region.y = original_y + y;

                        this._draw_checkbox_field(ctx, this.colors.permission.negate, x, y, PermissionEntry.CHECKBOX_HEIGHT, this.flag_negate, this.flag_negate_hovered);
                    }

                    w -= PermissionEntry.COLUMN_VALUE + PermissionEntry.COLUMN_PADDING;
                    if (this._permission.is_boolean()) {
                        const x = w + PermissionEntry.COLUMN_VALUE - PermissionEntry.CHECKBOX_HEIGHT;
                        const y = 1;

                        this._listener_value.region.width = PermissionEntry.CHECKBOX_HEIGHT;
                        this._listener_value.region.x = original_x + x;
                        this._listener_value.region.y = original_y + y;

                        this._draw_checkbox_field(ctx, this.colors.permission.value_b, x, y, PermissionEntry.CHECKBOX_HEIGHT, this.value > 0, this.flag_value_hovered);
                    } else if (this._permission.name === "i_icon_id" && this._icon_image) {
                        this._listener_value.region.x = original_x + w;
                        this._listener_value.region.y = original_y;
                        this._listener_value.region.width = PermissionEntry.CHECKBOX_HEIGHT;

                        this._draw_icon_field(ctx, this.colors.permission.value_b, w, 0, PermissionEntry.COLUMN_VALUE, this.flag_value_hovered, this._icon_image);
                    } else {
                        this._listener_value.region.width = PermissionEntry.COLUMN_VALUE;
                        this._listener_value.region.x = original_x + w;
                        this._listener_value.region.y = original_y;

                        this._draw_number_field(ctx, this.colors.permission.value, w, 0, PermissionEntry.COLUMN_VALUE, this.value, this.flag_value_hovered);
                    }
                    this._listener_value.disabled = false;
                } else {
                    this._listener_checkbox_skip.region.y = -1e8;
                    this._listener_checkbox_negate.region.y = -1e8;

                    this._listener_value.region.y = original_y;
                    this._listener_value.region.x =
                        original_x
                        + width
                        - PermissionEntry.COLUMN_GRANTED
                        - PermissionEntry.COLUMN_NEGATE
                        - PermissionEntry.COLUMN_VALUE
                        - PermissionEntry.COLUMN_PADDING * 4;
                    this._listener_value.disabled = true;
                }

                this._listener_general.region.y = original_y;
                this._listener_general.region.x = original_x;
            }

            handle_hide() {
                /* so the listener wound get triggered */
                this._listener_value.region.x = -1e8;
                this._listener_grant.region.x = -1e8;
                this._listener_checkbox_negate.region.x = -1e8;
                this._listener_checkbox_skip.region.x = -1e8;
                this._listener_general.region.x = -1e8;
            }

            private _draw_icon_field(ctx: CanvasRenderingContext2D, scheme: scheme.CheckBox, x: number, y: number, width: number, hovered: boolean, image: HTMLImageElement) {
                const line = ctx.lineWidth;
                ctx.lineWidth = 2;
                ctx.fillStyle = scheme.border;
                ctx.strokeRect(x + 1, y + 1, PermissionEntry.HEIGHT - 2, PermissionEntry.HEIGHT - 2);
                ctx.lineWidth = line;

                ctx.fillStyle = hovered ? scheme.background_hovered : scheme.background;
                ctx.fillRect(x + 1, y + 1, PermissionEntry.HEIGHT - 2, PermissionEntry.HEIGHT - 2);

                const center_y = y + PermissionEntry.HEIGHT / 2;
                const center_x = x + PermissionEntry.HEIGHT / 2;
                ctx.drawImage(image, center_x - image.width / 2, center_y - image.height / 2);
            }

            private _draw_number_field(ctx: CanvasRenderingContext2D, scheme: scheme.TextField, x: number, y: number, width: number, value: number, hovered: boolean) {
                ctx.fillStyle = hovered ? scheme.background_hovered : scheme.background;
                ctx.fillRect(x, y, width, PermissionEntry.HEIGHT);

                ctx.fillStyle = scheme.color;
                ctx.font = scheme.font; //Math.floor(2/3 * PermissionEntry.HEIGHT) + "px Arial";
                ctx.textAlign = "start";
                ctx.fillText(value + "", x, y + PermissionEntry.HALF_HEIGHT, width);

                ctx.strokeStyle = "#6e6e6e";
                const line = ctx.lineWidth;
                ctx.lineWidth = 2;
                ctx.beginPath();
                ctx.moveTo(x, y + PermissionEntry.HEIGHT - 2);
                ctx.lineTo(x + width, y + PermissionEntry.HEIGHT - 2);
                ctx.stroke();
                ctx.lineWidth = line;
            }

            private _draw_checkbox_field(ctx: CanvasRenderingContext2D, scheme: scheme.CheckBox, x: number, y: number, height: number, checked: boolean, hovered: boolean) {
                ctx.fillStyle = scheme.border;
                ctx.strokeRect(x, y, height, height);


                ctx.fillStyle = checked ?
                    (hovered ? scheme.background_checked_hovered : scheme.background_checked) :
                    (hovered ? scheme.background_hovered : scheme.background);
                ctx.fillRect(x + 1, y + 1, height - 2, height - 2);

                if (checked) {
                    ctx.textAlign = "center";
                    ctx.textBaseline = "middle";
                    ctx.fillStyle = scheme.checkmark;
                    ctx.font = scheme.checkmark_font; //Math.floor((5/4) * PermissionEntry.HEIGHT) + "px Arial";
                    ctx.fillText("✓", x + height / 2, y + height / 2);
                }
            }

            height() {
                return this.hidden ? 0 : PermissionEntry.HEIGHT;
            }

            set_width(value: number) {
                super.set_width(value);
                this._listener_general.region.width = value;
            }

            initialize() {
                this._listener_checkbox_skip = {
                    region: {
                        x: -1e8,
                        y: -1e8,
                        height: PermissionEntry.CHECKBOX_HEIGHT,
                        width: PermissionEntry.CHECKBOX_HEIGHT
                    },
                    region_weight: 10,
                    on_mouse_enter: () => {
                        this.flag_skip_hovered = true;
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    on_mouse_leave: () => {
                        this.flag_skip_hovered = false;
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    on_click: () => {
                        this.flag_skip = !this.flag_skip;
                        if (this.on_change)
                            this.on_change();
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    set_full_draw: () => this.request_full_draw(),
                    mouse_cursor: "pointer"
                };
                this._listener_checkbox_negate = {
                    region: {
                        x: -1e8,
                        y: -1e8,
                        height: PermissionEntry.CHECKBOX_HEIGHT,
                        width: PermissionEntry.CHECKBOX_HEIGHT
                    },
                    region_weight: 10,
                    on_mouse_enter: () => {
                        this.flag_negate_hovered = true;
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    on_mouse_leave: () => {
                        this.flag_negate_hovered = false;
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    on_click: () => {
                        this.flag_negate = !this.flag_negate;
                        if (this.on_change)
                            this.on_change();
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    set_full_draw: () => this.request_full_draw(),
                    mouse_cursor: "pointer"
                };
                this._listener_value = {
                    region: {
                        x: -1e8,
                        y: -1e8,
                        height: this._permission.is_boolean() ? PermissionEntry.CHECKBOX_HEIGHT : PermissionEntry.HEIGHT,
                        width: this._permission.is_boolean() ? PermissionEntry.CHECKBOX_HEIGHT : PermissionEntry.COLUMN_VALUE
                    },
                    region_weight: 10,
                    on_mouse_enter: () => {
                        this.flag_value_hovered = true;
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    on_mouse_leave: () => {
                        this.flag_value_hovered = false;
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    on_click: () => {
                        if (this._permission.is_boolean()) {
                            this.value = this.value > 0 ? 0 : 1;
                            if (this.on_change)
                                this.on_change();
                            return RepaintMode.REPAINT_OBJECT_FULL;
                        } else if (this._permission.name === "i_icon_id") {
                            this.on_icon_select(this.value).then(value => {
                                this.value = value;
                                if (this.on_change)
                                    this.on_change();
                            }).catch(error => {
                                console.warn(tr("Failed to select icon: %o"), error);
                            })
                        } else {
                            this._spawn_number_edit(
                                this._listener_value.region.x,
                                this._listener_value.region.y,
                                this._listener_value.region.width,
                                this._listener_value.region.height,
                                this.colors.permission.value,
                                this.value || 0,
                                value => {
                                    if (typeof (value) === "number") {
                                        this.value = value;
                                        this.request_full_draw();
                                        this.manager.request_draw(false);
                                        if (this.on_change)
                                            this.on_change();
                                    }
                                }
                            )
                        }
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    set_full_draw: () => this.request_full_draw(),
                    mouse_cursor: "pointer"
                };
                this._listener_grant = {
                    region: {
                        x: -1e8,
                        y: -1e8,
                        height: PermissionEntry.HEIGHT,
                        width: PermissionEntry.COLUMN_VALUE
                    },
                    region_weight: 10,
                    on_mouse_enter: () => {
                        this.flag_grant_hovered = true;
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    on_mouse_leave: () => {
                        this.flag_grant_hovered = false;
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    on_click: () => {
                        this._spawn_number_edit(
                            this._listener_grant.region.x,
                            this._listener_grant.region.y,
                            this._listener_grant.region.width,
                            this._listener_grant.region.height,
                            this.colors.permission.granted,
                            this.granted || 0, //TODO use max assignable value?
                            value => {
                                if (typeof (value) === "number") {
                                    this.granted = value;
                                    this.request_full_draw();
                                    this.manager.request_draw(false);

                                    if (this.on_grant_change)
                                        this.on_grant_change();
                                }
                            }
                        );
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    set_full_draw: () => this.request_full_draw(),
                    mouse_cursor: "pointer"
                };

                this._listener_general = {
                    region: {
                        x: -1e8,
                        y: -1e8,
                        height: PermissionEntry.HEIGHT,
                        width: 0
                    },
                    region_weight: 0,
                    /*
                    on_mouse_enter: () => {
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    on_mouse_leave: () => {
                        return RepaintMode.REPAINT_OBJECT_FULL;
                    },
                    */
                    on_click: (event: InteractionClickEvent) => {
                        this.manager.set_selected_entry(this);

                        if (event.type == ClickEventType.DOUBLE && typeof (this.value) === "undefined")
                            return this._listener_value.on_click(event);
                        else if (event.type == ClickEventType.CONTEXT_MENU) {
                            const mouse = this.manager.mouse;
                            if (this.on_context_menu) {
                                this.on_context_menu(mouse.x, mouse.y);
                                event.consumed = true;
                            }
                        }
                        return RepaintMode.NONE;
                    },
                    set_full_draw: () => this.request_full_draw(),
                };

                this.manager.intercept_manager().register_listener(this._listener_checkbox_negate);
                this.manager.intercept_manager().register_listener(this._listener_checkbox_skip);
                this.manager.intercept_manager().register_listener(this._listener_value);
                this.manager.intercept_manager().register_listener(this._listener_grant);
                this.manager.intercept_manager().register_listener(this._listener_general);
            }

            finalize() {
            }

            private _spawn_number_edit(x: number, y: number, width: number, height: number, color: scheme.TextField, value: number, callback: (new_value?: number) => any) {
                const element = $.spawn("div");
                element.prop("contentEditable", true);
                element
                    .css("pointer-events", "none")
                    .css("background", color.background)
                    .css("display", "block")
                    .css("position", "absolute")
                    .css("top", y)
                    .css("left", x)
                    .css("width", width)
                    .css("height", height)
                    .css("z-index", 1e6);
                element.text(value);
                element.appendTo(this.manager.canvas_container);
                element.focus();

                element.on('focusout', event => {
                    console.log("permission changed to " + element.text());
                    if (!isNaN(parseInt(element.text()))) {
                        callback(parseInt(element.text()));
                    } else {
                        callback(undefined);
                    }
                    element.remove();
                });

                element.on('keypress', event => {
                    if (event.which == KeyCode.KEY_RETURN)
                        element.trigger('focusout');

                    const text = String.fromCharCode(event.which);
                    if (isNaN(parseInt(text)) && text != "-")
                        event.preventDefault();

                    if (element.text().length > 7)
                        event.preventDefault();
                });

                if (window.getSelection) {
                    const selection = window.getSelection();
                    const range = document.createRange();
                    range.selectNodeContents(element[0]);
                    selection.removeAllRanges();
                    selection.addRange(range);
                }
            }

            trigger_value_assign() {
                this._listener_value.on_click(undefined);
            }

            trigger_grant_assign() {
                this._listener_grant.on_click(undefined);
            }
        }

        export class InteractionManager {
            private _listeners: InteractionListener[] = [];
            private _entered_listeners: InteractionListener[] = [];

            register_listener(listener: InteractionListener) {
                this._listeners.push(listener);
            }

            remove_listener(listener: InteractionListener) {
                this._listeners.remove(listener);
            }

            process_mouse_move(new_x: number, new_y: number): { repaint: RepaintMode, cursor: string } {
                let _entered_listeners: InteractionListener[] = [];
                for (const listener of this._listeners) {
                    const aabb = listener.region;

                    if (listener.disabled)
                        continue;

                    if (new_x < aabb.x || new_x > aabb.x + aabb.width)
                        continue;

                    if (new_y < aabb.y || new_y > aabb.y + aabb.height)
                        continue;

                    _entered_listeners.push(listener);
                }

                let repaint: RepaintMode = RepaintMode.NONE;
                _entered_listeners.sort((a, b) => (a.region_weight || 0) - (b.region_weight || 0));
                for (const listener of this._entered_listeners) {
                    if (listener.on_mouse_leave && _entered_listeners.indexOf(listener) == -1) {
                        let mode = listener.on_mouse_leave();
                        if (mode == RepaintMode.REPAINT_OBJECT_FULL) {
                            mode = RepaintMode.REPAINT;
                            if (listener.set_full_draw)
                                listener.set_full_draw();
                        }
                        if (mode > repaint)
                            repaint = mode;
                    }
                }
                for (const listener of _entered_listeners) {
                    if (listener.on_mouse_enter && this._entered_listeners.indexOf(listener) == -1) {
                        let mode = listener.on_mouse_enter();
                        if (mode == RepaintMode.REPAINT_OBJECT_FULL) {
                            mode = RepaintMode.REPAINT;
                            if (listener.set_full_draw)
                                listener.set_full_draw();
                        }
                        if (mode > repaint)
                            repaint = mode;
                    }
                }
                this._entered_listeners = _entered_listeners;

                let cursor;
                for (const listener of _entered_listeners)
                    if (typeof (listener.mouse_cursor) === "string") {
                        cursor = listener.mouse_cursor;
                    }
                return {
                    repaint: repaint,
                    cursor: cursor
                };
            }

            private process_click_event(x: number, y: number, event: InteractionClickEvent): RepaintMode {
                const move_result = this.process_mouse_move(x, y);

                let repaint: RepaintMode = move_result.repaint;
                for (const listener of this._entered_listeners)
                    if (listener.on_click) {
                        let mode = listener.on_click(event);
                        if (mode == RepaintMode.REPAINT_OBJECT_FULL) {
                            mode = RepaintMode.REPAINT;
                            if (listener.set_full_draw)
                                listener.set_full_draw();
                        }
                        if (mode > repaint)
                            repaint = mode;
                    }

                return repaint;
            }

            process_click(x: number, y: number): RepaintMode {
                const event: InteractionClickEvent = {
                    consumed: false,
                    type: ClickEventType.SIGNLE,
                    offset_x: x,
                    offset_y: y
                };

                return this.process_click_event(x, y, event);
            }

            process_dblclick(x: number, y: number): RepaintMode {
                const event: InteractionClickEvent = {
                    consumed: false,
                    type: ClickEventType.DOUBLE,
                    offset_x: x,
                    offset_y: y
                };

                return this.process_click_event(x, y, event);
            }

            process_context_menu(js_event: MouseEvent, x: number, y: number): RepaintMode {
                const event: InteractionClickEvent = {
                    consumed: js_event.defaultPrevented,
                    type: ClickEventType.CONTEXT_MENU,
                    offset_x: x,
                    offset_y: y
                };

                const result = this.process_click_event(x, y, event);
                if (event.consumed)
                    js_event.preventDefault();
                return result;
            }
        }

        export class PermissionEditor {
            private static readonly PERMISSION_HEIGHT = PermissionEntry.HEIGHT;
            private static readonly PERMISSION_GROUP_HEIGHT = PermissionGroup.HEIGHT;

            readonly grouped_permissions: GroupedPermissions[];
            readonly canvas: HTMLCanvasElement;
            readonly canvas_container: HTMLDivElement;
            private _max_height: number = 0;

            private _permission_count: number = 0;
            private _permission_group_count: number = 0;
            private _canvas_context: CanvasRenderingContext2D;

            private _selected_entry: PermissionEntry;

            private _draw_requested: boolean = false;
            private _draw_requested_full: boolean = false;

            private _elements: PermissionGroup[] = [];
            private _intersect_manager: InteractionManager;

            private _permission_entry_map: { [key: number]: PermissionEntry } = {};

            mouse: {
                x: number,
                y: number
            } = {
                x: 0,
                y: 0
            };

            constructor(permissions: GroupedPermissions[]) {
                this.grouped_permissions = permissions;

                this.canvas_container = $.spawn("div")
                    .addClass("window-resize-listener") /* we want to handle resized */
                    .css("min-width", "750px")
                    .css("position", "relative")
                    .css("user-select", "none")
                    [0];
                this.canvas = $.spawn("canvas")[0];

                this.canvas_container.appendChild(this.canvas);

                this._intersect_manager = new InteractionManager();
                this.canvas_container.onmousemove = event => {
                    this.mouse.x = event.pageX;
                    this.mouse.y = event.pageY;

                    const draw = this._intersect_manager.process_mouse_move(event.offsetX, event.offsetY);
                    this.canvas_container.style.cursor = draw.cursor || "";
                    this._handle_repaint(draw.repaint);
                };
                this.canvas_container.onclick = event => {
                    this._handle_repaint(this._intersect_manager.process_click(event.offsetX, event.offsetY));
                };
                this.canvas_container.ondblclick = event => {
                    this._handle_repaint(this._intersect_manager.process_dblclick(event.offsetX, event.offsetY));
                };
                this.canvas_container.oncontextmenu = (event: MouseEvent) => {
                    this._handle_repaint(this._intersect_manager.process_context_menu(event, event.offsetX, event.offsetY));
                };
                this.canvas_container.onresize = () => this.request_draw(true);


                this.initialize();
            }

            private _handle_repaint(mode: RepaintMode) {
                if (mode == RepaintMode.REPAINT || mode == RepaintMode.REPAINT_FULL)
                    this.request_draw(mode == RepaintMode.REPAINT_FULL);
            }

            request_draw(full?: boolean) {
                this._draw_requested_full = this._draw_requested_full || full;
                if (this._draw_requested)
                    return;
                this._draw_requested = true;
                requestAnimationFrame(() => {
                    this.draw(this._draw_requested_full);
                });
            }

            draw(full?: boolean) {
                this._draw_requested = false;
                this._draw_requested_full = false;

                /* clear max height */
                this.canvas_container.style.overflowY = "shown";
                this.canvas_container.style.height = undefined;

                const max_height = this._max_height;
                const max_width = this.canvas_container.clientWidth;
                const update_width = this.canvas.width != max_width;
                const full_draw = typeof (full) !== "boolean" || full || update_width;

                if (update_width) {
                    this.canvas.width = max_width;
                    for (const element of this._elements)
                        element.set_width(max_width);
                }

                console.log("Drawing%s on %dx%d", full_draw ? " full" : "", max_width, max_height);
                if (full_draw)
                    this.canvas.height = max_height;
                const ctx = this._canvas_context;
                ctx.resetTransform();
                if (full_draw)
                    ctx.clearRect(0, 0, max_width, max_height);

                let sum_height = 0;
                for (const element of this._elements) {
                    element.draw(ctx, full_draw);
                    const height = element.height();
                    sum_height += height;
                    ctx.translate(0, height);
                }

                this.canvas_container.style.overflowY = "hidden";
                this.canvas_container.style.height = sum_height + "px";
            }

            private initialize() {
                /* setup the canvas */
                {
                    const apply_group = (group: GroupedPermissions) => {
                        for (const g of group.children || [])
                            apply_group(g);
                        this._permission_group_count++;
                        this._permission_count += group.permissions.length;
                    };
                    for (const group of this.grouped_permissions)
                        apply_group(group);

                    this._max_height = this._permission_count * PermissionEditor.PERMISSION_HEIGHT + this._permission_group_count * PermissionEditor.PERMISSION_GROUP_HEIGHT;
                    console.log("%d permissions and %d groups required %d height", this._permission_count, this._permission_group_count, this._max_height);

                    this.canvas.style.width = "100%";

                    this.canvas.style.flexShrink = "0";
                    this.canvas_container.style.flexShrink = "0";

                    this._canvas_context = this.canvas.getContext("2d");
                }

                const font = Math.floor(2 / 3 * PermissionEntry.HEIGHT) + "px Arial";
                const font_checkmark = Math.floor((5 / 4) * PermissionEntry.HEIGHT) + "px Arial";
                const checkbox = {
                    background: "#303036",
                    background_hovered: "#CCCCCC",

                    background_checked: "#0000AA",
                    background_checked_hovered: "#0000AA77",

                    border: "#000000",
                    checkmark: "#303036",
                    checkmark_font: font_checkmark
                };
                const input: scheme.TextField = {
                    color: "#000000",
                    font: font,

                    background_hovered: "#CCCCCCCC",
                    background: "#30303600"
                };

                const color_scheme: scheme.ColorScheme = {
                    group: {
                        name: "#808080",
                        name_font: font
                    },
                    //#28282c
                    permission: {
                        name: "#808080",
                        name_unset: "#1a1a1a",
                        name_font: font,

                        background: "#303036",
                        background_selected: "#00007788",

                        value: input,
                        value_b: checkbox,
                        granted: input,
                        negate: checkbox,
                        skip: checkbox
                    }
                };
                (window as any).scheme = color_scheme;
                /* setup elements to draw */
                {
                    const process_group = (group: PermissionGroup) => {
                        for (const permission of group._element_permissions.permissions)
                            this._permission_entry_map[permission.permission().id] = permission;
                        for (const g of group._sub_elements)
                            process_group(g);
                    };

                    for (const group of this.grouped_permissions) {
                        const element = new PermissionGroup(group);
                        element.set_color_scheme(color_scheme);
                        element.set_manager(this);
                        process_group(element);
                        this._elements.push(element);
                    }
                    for (const element of this._elements) {
                        element.initialize();
                    }
                }
            }

            intercept_manager() {
                return this._intersect_manager;
            }

            set_selected_entry(entry?: PermissionEntry) {
                if (this._selected_entry === entry)
                    return;

                if (this._selected_entry) {
                    this._selected_entry.selected = false;
                    this._selected_entry.request_full_draw();
                }
                this._selected_entry = entry;
                if (this._selected_entry) {
                    this._selected_entry.selected = true;
                    this._selected_entry.request_full_draw();
                }
                this.request_draw(false);
            }

            permission_entries(): PermissionEntry[] {
                return Object.keys(this._permission_entry_map).map(e => this._permission_entry_map[e]);
            }

            collapse_all() {
                for (const group of this._elements)
                    group.collapse_group();
                this.request_draw(true);
            }

            expend_all() {
                for (const group of this._elements)
                    group.expend_group();
                this.request_draw(true);
            }
        }
    }

    export class CanvasPermissionEditor extends Modals.AbstractPermissionEditor {
        private container: JQuery;

        private mode_container_permissions: JQuery;
        private mode_container_error_permission: JQuery;
        private mode_container_unset: JQuery;

        /* references within the container tag */
        private permission_value_map: {[key:number]:PermissionValue} = {};

        private entry_editor: ui.PermissionEditor;

        icon_resolver: (id: number) => Promise<HTMLImageElement>;
        icon_selector: (current_id: number) => Promise<number>;

        constructor() {
            super();
        }

        initialize(permissions: GroupedPermissions[]) {
            this._permissions = permissions;
            this.entry_editor = new ui.PermissionEditor(permissions);
            this.build_tag();
        }

        html_tag() { return this.container; }

        private build_tag() {
            this.container = $("#tmpl_permission_editor_canvas").renderTag();
            /* search for that as long we've not that much nodes */
            this.mode_container_permissions = this.container.find(".container-mode-permissions");
            this.mode_container_error_permission = this.container.find(".container-mode-no-permissions");
            this.mode_container_unset = this.container.find(".container-mode-unset");
            this.set_mode(Modals.PermissionEditorMode.UNSET);

            /* the filter */
            {
                const tag_filter_input = this.container.find(".filter-input");
                const tag_filter_granted = this.container.find(".filter-granted");

                tag_filter_granted.on('change', event => tag_filter_input.trigger('change'));
                tag_filter_input.on('keyup change', event => {
                    let filter_mask = tag_filter_input.val() as string;
                    let req_granted = tag_filter_granted.prop("checked");


                    for(const entry of this.entry_editor.permission_entries()) {
                        const permission = entry.permission();

                        let shown = filter_mask.length == 0 || permission.name.indexOf(filter_mask) != -1;
                        if(shown && req_granted) {
                            const value: PermissionValue = this.permission_value_map[permission.id];
                            shown = value && (value.hasValue() || value.hasGrant());
                        }

                        entry.hidden = !shown;
                    }
                    this.entry_editor.request_draw(true);
                });
            }

            /* update button */
            {
                this.container.find(".button-update").on('click', this.trigger_update.bind(this));
            }

            /* global context menu listener */
            {
                this.container.on('contextmenu', event => {
                    if(event.isDefaultPrevented()) return;
                    event.preventDefault();

                    /* TODO allow collapse and expend all */
                });
            }

            {
                const tag_container = this.container.find(".entry-editor-container");
                tag_container.append(this.entry_editor.canvas_container);

                tag_container.parent().on('contextmenu', event => {
                    if(event.isDefaultPrevented()) return;
                    event.preventDefault();

                    contextmenu.spawn_context_menu(event.pageX, event.pageY, {
                        type: contextmenu.MenuEntryType.ENTRY,
                        name: tr("Expend all"),
                        callback: () => this.entry_editor.expend_all()
                    }, {
                        type: contextmenu.MenuEntryType.ENTRY,
                        name: tr("Collapse all"),
                        callback: () => this.entry_editor.collapse_all()
                    });
                });
            }

            /* setup the permissions */
            for(const entry of this.entry_editor.permission_entries()) {
                const permission = entry.permission();
                entry.on_change = () => {
                    const flag_remove = typeof(entry.value) !== "number";
                    this._listener_change(permission, {
                        remove: flag_remove,
                        flag_negate: entry.flag_negate,
                        flag_skip: entry.flag_skip,
                        value: flag_remove ? -2 : entry.value
                    }).then(() => {
                        if(flag_remove) {
                            const element = this.permission_value_map[permission.id];
                            if(!element) return; /* This should never happen, if so how are we displaying this permission?! */

                            element.value = undefined;
                            element.flag_negate = false;
                            element.flag_skip = false;
                        } else {
                            const element = this.permission_value_map[permission.id] || (this.permission_value_map[permission.id] = new PermissionValue(permission));

                            element.value = entry.value;
                            element.flag_skip = entry.flag_skip;
                            element.flag_negate = entry.flag_negate;
                        }

                        if(permission.name === "i_icon_id") {
                            this.icon_resolver(entry.value).then(e => {
                                entry.set_icon_id_image(e);
                                entry.request_full_draw();
                                this.entry_editor.request_draw(false);
                            }).catch(error => {
                                console.warn(tr("Failed to load icon for permission editor: %o"), error);
                            });
                        }
                        entry.request_full_draw();
                        this.entry_editor.request_draw(false);
                    }).catch(() => {
                        const element = this.permission_value_map[permission.id];

                        entry.value = element && element.hasValue() ? element.value : undefined;
                        entry.flag_skip = element && element.flag_skip;
                        entry.flag_negate = element && element.flag_negate;

                        entry.request_full_draw();
                        this.entry_editor.request_draw(false);
                    });
                };

                entry.on_grant_change = () => {
                    const flag_remove = typeof(entry.granted) !== "number";

                    this._listener_change(permission, {
                        remove: flag_remove,
                        granted: flag_remove ? -2 : entry.granted,
                    }).then(() => {
                        if(flag_remove) {
                            const element = this.permission_value_map[permission.id];
                            if (!element) return; /* This should never happen, if so how are we displaying this permission?! */

                            element.granted_value = undefined;
                        } else {
                            const element = this.permission_value_map[permission.id] || (this.permission_value_map[permission.id] = new PermissionValue(permission));
                            element.granted_value = entry.granted;
                        }
                        entry.request_full_draw();
                        this.entry_editor.request_draw(false);
                    }).catch(() => {
                        const element = this.permission_value_map[permission.id];

                        entry.granted = element && element.hasGrant() ? element.granted_value : undefined;
                        entry.request_full_draw();
                        this.entry_editor.request_draw(false);
                    });
                };

                entry.on_context_menu = (x, y) => {
                    let entries: contextmenu.MenuEntry[] = [];
                    if(typeof(entry.value) === "undefined") {
                        entries.push({
                            type: contextmenu.MenuEntryType.ENTRY,
                            name: tr("Add permission"),
                            callback: () => entry.trigger_value_assign()
                        });
                    } else {
                        entries.push({
                            type: contextmenu.MenuEntryType.ENTRY,
                            name: tr("Remove permission"),
                            callback: () => {
                                entry.value = undefined;
                                entry.on_change();
                            }
                        });
                    }

                    if(typeof(entry.granted) === "undefined") {
                        entries.push({
                            type: contextmenu.MenuEntryType.ENTRY,
                            name: tr("Add grant permission"),
                            callback: () => entry.trigger_grant_assign()
                        });
                    } else {
                        entries.push({
                            type: contextmenu.MenuEntryType.ENTRY,
                            name: tr("Remove grant permission"),
                            callback: () => {
                                entry.granted = undefined;
                                entry.on_grant_change();
                            }
                        });
                    }
                    entries.push(contextmenu.Entry.HR());
                    entries.push({
                        type: contextmenu.MenuEntryType.ENTRY,
                        name: tr("Expend all"),
                        callback: () => this.entry_editor.expend_all()
                    });
                    entries.push({
                        type: contextmenu.MenuEntryType.ENTRY,
                        name: tr("Collapse all"),
                        callback: () => this.entry_editor.collapse_all()
                    });
                    entries.push(contextmenu.Entry.HR());
                    entries.push({
                        type: contextmenu.MenuEntryType.ENTRY,
                        name: tr("Show permission description"),
                        callback: () => {
                            createInfoModal(
                                tr("Permission description"),
                                tr("Permission description for permission ") + permission.name + ": <br>" + permission.description
                            ).open();
                        }
                    });
                    entries.push({
                        type: contextmenu.MenuEntryType.ENTRY,
                        name: tr("Copy permission name"),
                        callback: () => {
                            copy_to_clipboard(permission.name);
                        }
                    });

                    contextmenu.spawn_context_menu(x, y, ...entries);
                }
            }
        }

        set_permissions(permissions?: PermissionValue[]) {
            permissions = permissions || [];
            this.permission_value_map = {};

            for(const permission of permissions)
                this.permission_value_map[permission.type.id] = permission;

            for(const entry of this.entry_editor.permission_entries()) {
                const permission = entry.permission();
                const value: PermissionValue = this.permission_value_map[permission.id];

                if(permission.name === "i_icon_id") {
                    entry.set_icon_id_image(undefined);
                    entry.on_icon_select = this.icon_selector;
                }

                if(value && value.hasValue()) {
                    entry.value = value.value;
                    entry.flag_skip = value.flag_skip;
                    entry.flag_negate = value.flag_negate;
                    if(permission.name === "i_icon_id") {
                        this.icon_resolver(value.value).then(e => {
                            entry.set_icon_id_image(e);
                            entry.request_full_draw();
                            this.entry_editor.request_draw(false);
                        }).catch(error => {
                            console.warn(tr("Failed to load icon for permission editor: %o"), error);
                        });
                    }
                } else {
                    entry.value = undefined;
                    entry.flag_skip = false;
                    entry.flag_negate = false;
                }

                if(value && value.hasGrant()) {
                    entry.granted = value.granted_value;
                } else {
                    entry.granted = undefined;
                }
            }
            this.entry_editor.request_draw(true);
        }

        set_mode(mode: Modals.PermissionEditorMode) {
            this.mode_container_permissions.css('display', mode == Modals.PermissionEditorMode.VISIBLE ? 'flex' : 'none');
            this.mode_container_error_permission.css('display', mode == Modals.PermissionEditorMode.NO_PERMISSION ? 'flex' : 'none');
            this.mode_container_unset.css('display', mode == Modals.PermissionEditorMode.UNSET ? 'block' : 'none');
            if(mode == Modals.PermissionEditorMode.VISIBLE)
                this.entry_editor.draw(true);
        }

        update_ui() {
            this.entry_editor.draw(true);
        }

        set_toggle_button(callback: () => string, initial: string) {
            throw "not implemented";
        }

        set_hidden_permissions(permissions: PermissionType[]) {
            //TODO: Stuff here
        }
    }
}