/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.jboss.as.cli.gui.component;

import java.io.IOException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.RowSorter;
import javax.swing.SwingConstants;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;

import org.jboss.as.cli.gui.CliGuiContext;
import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.as.controller.client.helpers.Operations;
import org.jboss.as.controller.client.helpers.Operations.CompositeOperationBuilder;
import org.jboss.dmr.ModelNode;

/**
 * The TableModel for the server logs.
 *
 * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
 */
public class ServerLogsTableModel extends AbstractTableModel {
    private static final char[] SIZE_CHARS = {'K', 'M', 'G', 'T', 'P', 'E'};
    private CliGuiContext cliGuiCtx;
    private List<ModelNode> allLogs;
    private ServerLogsTable table;
    private final AtomicBoolean initialized = new AtomicBoolean(false);
    private final TableCellRenderer dateRenderer = new DefaultTableCellRenderer() {
        @Override
        protected void setValue(final Object value) {
            final DateFormat formatter = DateFormat.getDateTimeInstance();
            setText(value == null ? "" : formatter.format(value));
        }
    };
    private final DefaultTableCellRenderer sizeRenderer = new DefaultTableCellRenderer() {
        protected void setValue(final Object value) {
            final String result;
            if (value instanceof Long) {
                final long len = (long) value;
                final int unit = 1000;
                if (len < unit) {
                    result = len + "B";
                } else {
                    final int exp = (int) (Math.log(len) / Math.log(unit));
                    result = String.format("%.1f %sB", len / Math.pow(unit, exp), SIZE_CHARS[exp - 1]);
                }
            } else {
                result = "";
            }
            setText(result);
        }
    };

    protected final String[] colNames = new String[] {"File", "Last Modified", "Size"};

    public ServerLogsTableModel(CliGuiContext cliGuiCtx, ServerLogsTable table) {
        this.cliGuiCtx = cliGuiCtx;
        this.table = table;
        sizeRenderer.setHorizontalAlignment(SwingConstants.RIGHT);
    }

    /**
     * Initializes the model
     */
    private void init() {
        if (initialized.compareAndSet(false, true)) {
            final RowSorter<? extends TableModel> rowSorter = table.getRowSorter();
            rowSorter.toggleSortOrder(1); // sort by date
            rowSorter.toggleSortOrder(1); // descending
            final TableColumnModel columnModel = table.getColumnModel();
            columnModel.getColumn(1).setCellRenderer(dateRenderer);
            columnModel.getColumn(2).setCellRenderer(sizeRenderer);
        }
    }

    public void refresh() {
        try {
            init();
            this.allLogs = getLogFiles();
            fireTableDataChanged();
            if (!allLogs.isEmpty()) table.setRowSelectionInterval(0, 0);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int getRowCount() {
        if (allLogs == null) return 0;
        return allLogs.size();
    }

    @Override
    public int getColumnCount() {
        return colNames.length;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        ModelNode row = allLogs.get(rowIndex);
        if (columnIndex == 0) return row.get("file-name").asString();
        if (columnIndex == 1) {
            return new Date(row.get("last-modified-time").asLong());
        }

        // column 2
        if (columnIndex == 2) {
            return row.get("file-size").asLong();
        }
        return null;
    }

    @Override
    public String getColumnName(int column) {
        return colNames[column];
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        if (columnIndex == 1) {
            return Date.class;
        } else if (columnIndex == 2) {
            return Long.class;
        }
        return String.class;
    }

    private List<ModelNode> getLogFiles() throws IOException {
        final ModelControllerClient client = cliGuiCtx.getCommmandContext().getModelControllerClient();
        final ModelNode address = new ModelNode().setEmptyList();
        address.add("subsystem", "logging");
        // address.add("log-file", "*");
        final ModelNode op = Operations.createOperation("read-children-names", address);
        op.get("child-type").set("log-file");
        ModelNode response = client.execute(op);
        if (Operations.isSuccessfulOutcome(response)) {
            ModelNode result = Operations.readResult(response);
            final Collection<String> files = new ArrayList<>();
            final CompositeOperationBuilder builder = CompositeOperationBuilder.create();
            for (ModelNode file : result.asList()) {
                files.add(file.asString());
                builder.addStep(Operations.createReadAttributeOperation(address.clone().add("log-file", file.asString()), "file-size"))
                        .addStep(Operations.createReadAttributeOperation(address.clone().add("log-file", file.asString()), "last-modified-time"));
            }
            result = client.execute(builder.build());
            ModelNode fileListing = new ModelNode().setEmptyList();
            if (Operations.isSuccessfulOutcome(result)) {
                final List<ModelNode> attributes = Operations.readResult(result).asList();
                int i = 0;
                if (attributes.size() != (files.size() * 2)) {
                    throw new IllegalStateException("Error occurred reading the file attributes");
                }
                // Each file result will have two step results from the composite operation
                for (String file : files) {
                    final ModelNode node = new ModelNode();
                    node.get("file-name").set(file);
                    node.get("file-size").set(Operations.readResult(attributes.get(i++).get(0)));
                    node.get("last-modified-time").set(Operations.readResult(attributes.get(i++).get(0)));
                    fileListing.add(node);
                }
            }
            return fileListing.asList();
        }
        throw new RuntimeException(Operations.getFailureDescription(response).asString());
    }

}
