package com.simon816.chatui.tabs.perm;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.simon816.chatui.lib.PlayerChatView;
import com.simon816.chatui.lib.PlayerContext;
import com.simon816.chatui.ui.AnchorPaneUI;
import com.simon816.chatui.ui.LineFactory;
import com.simon816.chatui.ui.UIComponent;
import com.simon816.chatui.ui.table.DefaultColumnRenderer;
import com.simon816.chatui.ui.table.DefaultTableRenderer;
import com.simon816.chatui.ui.table.TableColumnRenderer;
import com.simon816.chatui.ui.table.TableModel;
import com.simon816.chatui.ui.table.TableScrollHelper;
import com.simon816.chatui.ui.table.TableUI;
import com.simon816.chatui.util.ExtraUtils;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.service.context.Context;
import org.spongepowered.api.service.permission.Subject;
import org.spongepowered.api.service.permission.SubjectReference;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.action.TextActions;
import org.spongepowered.api.text.format.TextColor;
import org.spongepowered.api.text.format.TextColors;
import org.spongepowered.api.text.format.TextStyles;
import org.spongepowered.api.util.Tristate;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

import javax.swing.SwingConstants;

class SubjectViewer extends AnchorPaneUI {

    final PermissionsTab tab;

    Subject activeSubj;
    Set<Context> activeContext = Collections.emptySet();

    private List<Entry<String, Boolean>> permList;

    TableScrollHelper tableScroll;

    boolean addMode;
    boolean addParentMode;

    public SubjectViewer(PermissionsTab tab) {
        this.tab = tab;
        addWithConstraint(createTopBar(), ANCHOR_TOP);
        addChildren(createTable(), createEmptyMessage());
        addWithConstraint(createBottomBar(), ANCHOR_BOTTOM);
    }

    public List<Entry<String, Boolean>> getPerms() {
        if (this.permList == null) {
            Map<String, Boolean> map = this.activeSubj.getSubjectData().getPermissions(this.activeContext);
            this.permList = Lists.newArrayList();
            for (Entry<String, Boolean> entry : map.entrySet()) {
                this.permList.add(entry);
            }
        }
        this.permList.sort((a, b) -> a.getKey().compareToIgnoreCase(b.getKey()));
        return this.permList;
    }

    private TableModel createTableModel() {
        return new TableModel() {

            @Override
            public int getRowCount() {
                return getPerms().size();
            }

            @Override
            public int getColumnCount() {
                return 3;
            }

            @Override
            public Object getCellValue(int row, int column) {
                Entry<String, Boolean> entry = getPerms().get(row);
                if (column == 0) {
                    return entry.getKey();
                } else if (column == 1) {
                    return entry.getValue();
                }
                return null;
            }
        };
    }

    private TableUI createTable() {
        TableModel model = createTableModel();
        this.tableScroll = new TableScrollHelper(model);
        return new TableUI(model, new DefaultTableRenderer() {

            private final TableViewport viewport = SubjectViewer.this.tableScroll.createViewport();

            @Override
            public TableViewport getViewport() {
                return this.viewport;
            }

            @Override
            protected int getCellAlignment(int row, int column) {
                if (column == 2) {
                    return SwingConstants.CENTER;
                }
                return super.getCellAlignment(row, column);
            }

            @Override
            public TableColumnRenderer createColumnRenderer(int columnIndex) {
                if (columnIndex == 0) {
                    return new DefaultColumnRenderer() {

                        @Override
                        public List<Text> renderCell(Object value, int row, int tableWidth, PlayerContext ctx) {
                            return ctx.utils().splitLines(Text.of(ExtraUtils.clickAction(view -> {
                                toggle(view.getPlayer(), row);
                            }, SubjectViewer.this.tab), value), tableWidth - getPrefWidth());
                        }
                    };
                }
                if (columnIndex == 1) {
                    return new DefaultColumnRenderer() {

                        @Override
                        public List<Text> renderCell(Object value, int row, int tableWidth, PlayerContext ctx) {
                            boolean v = (Boolean) value;
                            TextColor color = v ? TextColors.GREEN : TextColors.RED;
                            return ctx.utils().splitLines(Text.of(color, ExtraUtils.clickAction(view -> {
                                toggle(view.getPlayer(), row);
                            }, SubjectViewer.this.tab), value), getPrefWidth());
                        }
                    };
                }
                if (columnIndex == 2) {
                    return new DefaultColumnRenderer() {

                        @Override
                        public List<Text> renderCell(Object value, int row, int tableWidth, PlayerContext ctx) {
                            return Collections.singletonList(Text.of(TextColors.RED, ExtraUtils.clickAction(view -> {
                                delete(view.getPlayer(), row);
                            }, SubjectViewer.this.tab), "X"));
                        }
                    };
                }
                return super.createColumnRenderer(columnIndex);
            }
        });
    }

    void toggle(Player player, int index) {
        Entry<String, Boolean> entry = getPerms().get(index);
        if (set(player, index, Tristate.fromBoolean(!entry.getValue())).join()) {
            this.permList = null;
        }
    }

    private CompletableFuture<Boolean> set(Player player, int index, Tristate value) {
        Entry<String, Boolean> entry = getPerms().get(index);
        return this.tab.actions().setPermission(player, this.activeSubj, this.activeContext, entry.getKey(), value);
    }

    void delete(Player player, int index) {
        if (set(player, index, Tristate.UNDEFINED).join()) {
            getPerms().remove(index);
        }
    }

    private UIComponent createEmptyMessage() {
        return new UIComponent() {

            @Override
            public void draw(PlayerContext ctx, LineFactory lineFactory) {
                if (getPerms().size() == 0) {
                    lineFactory.appendNewLine(Text.of("No permissions set"), ctx);
                }
            }
        };
    }

    Text hyperlink(String text, Runnable r) {
        return hyperlink(text, r, null);
    }

    Text hyperlink(String text, Runnable r, String hoverText) {
        Text.Builder b = Text.builder(text).color(TextColors.BLUE).style(TextStyles.UNDERLINE)
                .onClick(ExtraUtils.clickAction(r, this.tab));
        if (hoverText != null) {
            b.onHover(TextActions.showText(Text.of(hoverText)));
        }
        return b.build();
    }

    private UIComponent createTopBar() {
        return new UIComponent() {

            @Override
            public void draw(PlayerContext ctx, LineFactory lineFactory) {
                lineFactory.appendNewLine(Text.of(TextStyles.BOLD, TextColors.RED, SubjectViewer.this.activeSubj.getIdentifier()), ctx);
                Text.Builder builder = Text.builder("Parents: ");
                if (SubjectViewer.this.activeSubj.getParents(SubjectViewer.this.activeContext).isEmpty()) {
                    builder.append(Text.of("None"));
                }
                for (SubjectReference parent : SubjectViewer.this.activeSubj.getParents(SubjectViewer.this.activeContext)) {
                    builder.append(hyperlink(parent.getSubjectIdentifier(), () -> setActive(parent.resolve().join(), false)));
                    builder.append(Text.builder("[x]").color(TextColors.RED)
                            .onClick(ExtraUtils.clickAction((Consumer<PlayerChatView>) view -> removeParent(view.getPlayer(), parent),
                                    SubjectViewer.this.tab))
                            .build());
                    builder.append(Text.of(", "));
                }
                lineFactory.appendNewLine(builder.build(), ctx);
                builder = Text.builder("Current Context: ");
                if (SubjectViewer.this.activeContext.isEmpty()) {
                    builder.append(Text.of("global"));
                }
                for (Context context : SubjectViewer.this.activeContext) {
                    builder.append(Text.of(context.getKey() + "[" + context.getValue() + "], "));
                }
                lineFactory.appendNewLine(builder.build(), ctx);
            }
        };
    }

    private UIComponent createBottomBar() {
        PermissionsTab tab = this.tab;
        return new UIComponent() {

            @Override
            public void draw(PlayerContext ctx, LineFactory lineFactory) {
                lineFactory.appendNewLine(Text.builder()
                        .append(
                                simpleLink(SubjectViewer.this.addMode ? TextColors.RED : TextColors.GREEN,
                                        SubjectViewer.this.addMode ? "[Cancel]" : "[Add]", () -> {
                                            SubjectViewer.this.addMode = !SubjectViewer.this.addMode;
                                        }),
                                simpleLink(SubjectViewer.this.addParentMode ? TextColors.RED : TextColors.DARK_GREEN,
                                        SubjectViewer.this.addParentMode ? " [Cancel]" : " [Add Parent]", () -> {
                                            SubjectViewer.this.addParentMode = !SubjectViewer.this.addParentMode;
                                        }),
                                simpleLink(SubjectViewer.this.tableScroll.canScrollUp() ? TextColors.WHITE : TextColors.GRAY, " [Scroll Up]", () -> {
                                    SubjectViewer.this.tableScroll.scrollUp();
                                }),
                                simpleLink(SubjectViewer.this.tableScroll.canScrollDown() ? TextColors.WHITE : TextColors.GRAY, " [Scroll Down]",
                                        () -> {
                                            SubjectViewer.this.tableScroll.scrollDown();
                                        }))
                        .build(), ctx);
                lineFactory.appendNewLine(Text.builder()
                        .append(
                                simpleLink(TextColors.BLUE, "[Return]", () -> {
                                    goBack();
                                }),
                                simpleLink(TextColors.GOLD, " [Options]", view -> {
                                    tab.getEntryDisplayer()
                                            .setData(SubjectViewer.this.activeSubj.getSubjectData()
                                                    .getOptions(SubjectViewer.this.activeContext),
                                                    (key, value) -> createOption(view.getPlayer(), key, value),
                                                    key -> removeOption(view.getPlayer(), key),
                                                    () -> tab.setRoot(SubjectViewer.this));

                                    tab.setRoot(tab.getEntryDisplayer());
                                }),
                                simpleLink(TextColors.DARK_AQUA, " Default: " + getDefault().toString(), view -> {
                                    cycleDefault(view.getPlayer());
                                }))
                        .build(), ctx);
            }

            private Text simpleLink(TextColor color, String text, Runnable r) {
                return Text.builder(text).color(color).onClick(ExtraUtils.clickAction(r, tab)).build();
            }

            private Text simpleLink(TextColor color, String text, Consumer<PlayerChatView> c) {
                return Text.builder(text).color(color).onClick(ExtraUtils.clickAction(c, tab)).build();
            }

        };
    }

    Map.Entry<String, String> createOption(Player player, String key, String value) {
        if (this.tab.actions().setOption(player, this.activeSubj, this.activeContext, key, value).join()) {
            return Maps.immutableEntry(key, value);
        }
        return null;
    }

    boolean removeOption(Player player, String option) {
        return this.tab.actions().setOption(player, this.activeSubj, this.activeContext, option, null).join();
    }

    void cycleDefault(Player player) {
        Tristate[] vals = Tristate.values();
        Tristate val = vals[(getDefault().ordinal() + 1) % vals.length];
        this.tab.actions().setDefault(player, this.activeSubj, this.activeContext, val);
    }

    Tristate getDefault() {
        return this.tab.actions().getDefault(this.activeSubj, this.activeContext);
    }

    private final List<Subject> history = Lists.newArrayList();

    void goBack() {
        if (!this.history.isEmpty()) {
            this.activeSubj = null;
            setActive(this.history.remove(this.history.size() - 1), false);
        } else {
            this.tab.getSubjListPane().setSubjectList(SubjectViewer.this.activeSubj.getContainingCollection());
            this.tab.setRoot(this.tab.getSubjListPane());
        }
    }

    public void setActive(Subject subject, boolean clearHistory) {
        if (clearHistory) {
            this.history.clear();
        } else if (this.activeSubj != null) {
            this.history.add(this.activeSubj);
        }
        this.permList = null;
        this.activeSubj = subject;
        this.tableScroll.reset();
    }

    public void onTextEntered(PlayerChatView view, Text input) {
        if (this.addMode) {
            add(view.getPlayer(), input.toPlain());
            this.addMode = false;
        } else if (this.addParentMode) {
            addParent(view.getPlayer(), input.toPlain());
            this.addParentMode = false;
        } else {
            return;
        }
        view.update();
    }

    private void addParent(Player player, String parentIdentifier) {
        this.tab.actions().addParent(player, this.activeSubj, this.activeContext, parentIdentifier);
    }

    void removeParent(Player player, SubjectReference parent) {
        this.tab.actions().removeParent(player, this.activeSubj, this.activeContext, parent);
    }

    private boolean add(Player player, String permission) {
        if (this.tab.actions().setPermission(player, this.activeSubj, this.activeContext, permission, Tristate.TRUE).join()) {
            this.permList = null;
            return true;
        }
        return false;
    }

}