/*
* Apache License
* Version 2.0, January 2004
* http://www.apache.org/licenses/
*
* Copyright 2013 Aurelian Tutuianu
* Copyright 2014 Aurelian Tutuianu
* Copyright 2015 Aurelian Tutuianu
* Copyright 2016 Aurelian Tutuianu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package rapaio.printer.format;
import rapaio.printer.Printable;
import rapaio.sys.WS;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Created by <a href="mailto:padreati@yahoo.com">Aurelian Tutuianu</a> at 12/16/14.
*/
public class TextTable implements Printable {
private final int rows;
private final int cols;
private final String[][] values;
private final int[][] mergeCols;
private final int[][] alignCells;
private int headerRows = 0;
private int headerCols = 0;
private int hSplitSize = -1;
private int hMergeSize = -1;
private TextTable(int rows, int cols) {
this.rows = rows;
this.cols = cols;
values = new String[rows][cols];
mergeCols = new int[rows][cols];
alignCells = new int[rows][cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
values[i][j] = "";
mergeCols[i][j] = 1;
alignCells[i][j] = -1;
}
}
}
public static TextTable newEmpty(int rows, int cols) {
return new TextTable(rows, cols);
}
public int rows() {
return rows;
}
public int cols() {
return cols;
}
public TextTable withSplit() {
return withSplit(0);
}
public TextTable withSplit(int width) {
this.hSplitSize = (width == 0) ? WS.getPrinter().textWidth() : width;
return this;
}
public TextTable withMerge() {
return withMerge(0);
}
public TextTable withMerge(int width) {
this.hMergeSize = width == 0 ? WS.getPrinter().textWidth() : width;
return this;
}
public TextTable withHeaderRows(int headerRows) {
if (headerRows < 0)
headerRows = 0;
if (headerRows > rows) {
throw new IllegalArgumentException("cannot set header rows greater than the number of rows");
}
this.headerRows = headerRows;
return this;
}
public TextTable withHeaderCols(int headerCols) {
if (headerCols < 0)
headerCols = 0;
if (headerCols > cols) {
throw new IllegalArgumentException("cannot set header cols greater than the number of cols");
}
this.headerCols = headerCols;
return this;
}
public String get(int row, int col) {
return values[row][col];
}
public void set(int row, int col, String x, int align) {
values[row][col] = x;
alignCells[row][col] = align;
}
public void mergeCols(int row, int col, int size) {
if (size < 1) {
throw new IllegalArgumentException("cannot merge with size less than 1");
}
if (size + col - 1 >= cols) {
throw new IllegalArgumentException("merge size goes outside boundaries");
}
mergeCols[row][col] = size;
}
@Override
public String summary() {
StringBuilder sb = new StringBuilder();
if (hSplitSize != -1 && hMergeSize != -1) {
throw new IllegalArgumentException("Cannot set hSplitSize >= 0 and hMergeSize >= 0 in the same time");
}
if (hSplitSize >= 0) {
summaryHSplit(sb);
} else if (hMergeSize >= 0) {
summaryHMerge(sb);
} else {
summarySame(sb);
}
return sb.toString();
}
private void summaryHSplit(StringBuilder sb) {
int[] ws = computeLayout();
boolean[] cannotSplit = new boolean[cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
for (int k = 1; k < mergeCols[i][j]; k++) {
cannotSplit[j + k - 1] = true;
}
}
}
List<List<Integer>> splits = new ArrayList<>();
int s = 0;
int c = headerCols;
int w = 0;
while (c < cols) {
if (splits.size() < s + 1) {
splits.add(new ArrayList<>());
if (headerCols > 0) {
for (int i = 0; i < headerCols; i++) {
w += ws[i];
splits.get(s).add(i);
}
}
}
int wNext = 0;
int cc = c;
wNext += ws[cc];
cc++;
while (cc < cols && cannotSplit[cc]) {
wNext += ws[cc];
cc++;
}
if (splits.get(s).isEmpty() || w + wNext <= hSplitSize) {
for (int i = c; i < cc; i++) {
splits.get(s).add(i);
w += wNext;
}
c = cc;
} else {
w = 0;
s++;
}
}
int offset = 0;
for (List<Integer> indexes : splits) {
TextTable tt = new TextTable(rows, indexes.size());
for (int j = 0; j < rows; j++) {
for (int k = 0; k < indexes.size(); k++) {
tt.set(j, k, get(j, indexes.get(k)), alignCells[j][indexes.get(k)]);
tt.mergeCols(j, k, mergeCols[j][indexes.get(k)]);
}
}
tt.summarySame(sb);
offset += indexes.size();
sb.append("\n");
}
}
private void summaryHMerge(StringBuilder sb) {
int[] ws = computeLayout();
int total = Arrays.stream(ws).sum();
int all = hMergeSize - total;
int times = 1;
while (all > total) {
times++;
all -= total;
}
if (times == 1) {
summarySame(sb);
return;
}
int contentRows = rows - headerRows;
times = Math.min(contentRows, times);
int maxContent = (int) Math.ceil(1.0 * contentRows / times);
TextTable tt = TextTable.newEmpty(headerRows + maxContent, cols * times);
tt.withHeaderRows(headerRows);
int start = headerRows;
for (int i = 0; i < times; i++) {
// copy header
for (int j = 0; j < headerRows; j++) {
for (int k = 0; k < cols; k++) {
tt.set(j, i * cols + k, get(j, k), alignCells[j][k]);
tt.mergeCols(j, i * cols + k, mergeCols[j][k]);
}
}
// copy content
for (int j = 0; j < maxContent; j++) {
for (int k = 0; k < cols; k++) {
if (start < rows) {
tt.set(j + headerRows, i * cols + k, get(start, k), alignCells[start][k]);
tt.mergeCols(j + headerRows, i * cols + k, mergeCols[start][k]);
} else {
break;
}
}
start++;
}
}
tt.summarySame(sb);
}
private void summarySame(StringBuilder sb) {
int[] ws = computeLayout();
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (mergeCols[i][j] > 1) {
int w = 0;
for (int k = j; k < j + mergeCols[i][j]; k++) {
w += ws[k];
}
sb.append(align(alignCells[i][j], w, values[i][j]));
j += (mergeCols[i][j] - 1);
} else {
sb.append(" ").append(align(alignCells[i][j], ws[j] - 1, values[i][j]));
}
}
sb.append("\n");
}
}
private String align(int align, int width, String text) {
if (align < 0) {
if (text.length() < width) {
return text + spaces(width - text.length());
}
} else if (align > 0) {
if (text.length() < width) {
return spaces(width - text.length()) + text;
}
} else {
if (text.length() < width) {
int half = (width - text.length()) / 2;
return spaces(width - text.length() - half) + text + spaces(half);
}
}
return text;
}
private String spaces(int n) {
StringBuilder outputBuffer = new StringBuilder(n);
for (int i = 0; i < n; i++) {
outputBuffer.append(" ");
}
return outputBuffer.toString();
}
private int[] computeLayout() {
int[] ws = new int[cols];
int[] wm = new int[cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (mergeCols[i][j] > 1) {
wm[j] = Math.max(values[i][j].length(), wm[j]);
j += (mergeCols[i][j] - 1);
} else {
ws[j] = Math.max(values[i][j].length() + 1, ws[j]);
}
}
}
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (mergeCols[i][j] > 1) {
int sum = 0;
for (int k = j + 1; k < j + mergeCols[i][j]; k++) {
sum += ws[k];
}
ws[j] = Math.max(wm[j] - sum, ws[j]);
}
}
}
return ws;
}
}