/*
 * Minecraft Forge
 * Copyright (c) 2016-2019.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation version 2.1
 * of the License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

package net.minecraftforge.common.util;

import com.google.common.collect.Streams;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * Utility to format data into a textual (markdown-compliant) table.
 */
public class TextTable
{
    public static Column column(String header)
    {
        return new Column(header);
    }

    public static Column column(String header, Alignment alignment)
    {
        return new Column(header, alignment);
    }

    private final List<Column> columns;
    private final List<Row> rows = new ArrayList<>();

    public TextTable(List<Column> columns)
    {
        this.columns = columns;
    }

    public String build(String lineEnding)
    {
        StringBuilder destination = new StringBuilder();
        append(destination, lineEnding);
        return destination.toString();
    }

    /**
     * Appends the data formatted as a table to the given string builder.
     * The padding character used for the column alignments is a single space (' '),
     * the separate between column headers and values is a dash ('-').
     * Note that you *have* to specify a line ending, '\n' isn't used by default.
     * <p>
     * The generated table is compliant with the markdown file format.
     *
     * @param destination a string builder to append the table to
     * @param lineEnding  the line ending to use for each row of the table
     */
    public void append(StringBuilder destination, String lineEnding)
    {
        List<String> headers = columns.stream().map(c -> c.formatHeader(" ")).collect(Collectors.toList());
        printRow(destination, headers);
        destination.append(lineEnding);
        printSeparators(destination);
        for (Row row : rows)
        {
            destination.append(lineEnding);
            printRow(destination, row.format(columns, " "));
        }
    }

    private void printSeparators(StringBuilder destination)
    {
        destination.append('|');
        for (Column column : columns)
        {
            destination.append(column.alignment != Alignment.RIGHT ? ':' : ' ');
            destination.append(column.getSeparator('-'));
            destination.append(column.alignment != Alignment.LEFT ? ':' : ' ');
            destination.append('|');
        }
    }

    private void printRow(StringBuilder destination, List<String> values)
    {
        destination.append('|');
        for (String value : values)
        {
            destination.append(' ');
            destination.append(value);
            destination.append(' ');
            destination.append('|');
        }
    }

    public void add(@Nonnull Object... values)
    {
        if (values.length != columns.size())
        {
            throw new IllegalArgumentException("Received wrong amount of values for table row, expected " + columns.size() + ", received " + columns.size() + ".");
        }
        Row row = new Row();
        for (int i = 0; i < values.length; i++)
        {
            String value = Objects.toString(values[i]);
            row.values.add(value);
            columns.get(i).fit(value);
        }
        rows.add(row);
    }

    public void clear()
    {
        for (Column column : columns)
        {
            column.resetWidth();
        }
        rows.clear();
    }

    public List<Column> getColumns()
    {
        return Collections.unmodifiableList(columns);
    }

    public static class Column
    {
        private String header;
        private int width;
        private Alignment alignment;

        public Column(String header)
        {
            this(header, Alignment.LEFT);
        }

        public Column(String header, Alignment alignment)
        {
            this.header = header;
            this.width = header.length();
            this.alignment = alignment;
        }

        public String formatHeader(String padding)
        {
            return format(header, padding);
        }

        public String format(String value, String padding)
        {
            switch (alignment)
            {
                case LEFT:
                    return StringUtils.rightPad(value, width, padding);
                case RIGHT:
                    return StringUtils.leftPad(value, width, padding);
                default:
                    int length = value.length();
                    int left = (width - length) / 2;
                    int leftWidth = left + length;
                    return StringUtils.rightPad(StringUtils.leftPad(value, leftWidth, padding), width, padding);
            }
        }

        public String getSeparator(char character)
        {
            return StringUtils.leftPad("", width, character);
        }

        public void fit(String value)
        {
            if (value.length() > width)
            {
                width = value.length();
            }
        }

        public void resetWidth()
        {
            this.width = header.length();
        }

        public int getWidth()
        {
            return width;
        }
    }

    public static class Row
    {
        private final ArrayList<String> values = new ArrayList<>();

        public List<String> format(List<Column> columns, String padding)
        {
            if (columns.size() != values.size())
            {
                throw new IllegalArgumentException("Received wrong amount of columns for table row, expected " + columns.size() + ", received " + columns.size() + ".");
            }
            return Streams.zip(values.stream(), columns.stream(), (v, c) -> c.format(v, padding)).collect(Collectors.toList());
        }
    }

    public enum Alignment
    {
        LEFT, CENTER, RIGHT
    }
}