/*******************************************************************************
 * Copyright (c) 2012, 2020 Original authors and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Original authors and others - initial API and implementation
 ******************************************************************************/
package org.eclipse.nebula.widgets.nattable.dataset.generator;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.eclipse.nebula.widgets.nattable.dataset.valuegenerator.UppercaseStringValueGenerator;

public class NatTableDataGenerator {

    private int numRows = 0;

    private int numCols = 0;

    private List<ColumnDataType> columnDataTypes;

    private static DataGenerator<DoubleColumnValueBean> doubleGenerator = new DataGenerator<DoubleColumnValueBean>();

    private static DataGenerator<StringColumnValueBean> stringGenerator = new DataGenerator<StringColumnValueBean>();

    enum ColumnDataType {
        STRING_DATA, DOUBLE_DATA
    };

    public NatTableDataGenerator(int numCols, final int numRows) {
        this.numRows = numRows;
        this.numCols = numCols;
        initColumnDataTypes();
    }

    public NatTableDataGenerator() {
    }

    private void initColumnDataTypes() {
        this.columnDataTypes = new ArrayList<ColumnDataType>();
        for (int i = 0; i < this.numCols; i++) {
            ColumnDataType dataType = ColumnDataType.values()[(int) (Math.random() * ColumnDataType.values().length)];
            this.columnDataTypes.add(dataType);
        }
    }

    public void persistData(final String fileName) throws IOException, GeneratorException {
        try (BufferedWriter out = new BufferedWriter(new FileWriter(fileName))) {
            // first write the data types
            for (int i = 0; i < this.columnDataTypes.size(); i++) {
                ColumnDataType dataType = this.columnDataTypes.get(i);
                out.write(dataType.toString());
                out.write(i == this.columnDataTypes.size() - 1 ? "" : ",");
            }

            out.newLine();
            String text = "Writing to " + fileName;
            int charsWritten = text.length();
            System.out.print(text);
            for (int i = 0; i < this.numRows; i++) {
                for (int j = 0; j < this.numCols; j++) {
                    ColumnDataType dataType = this.columnDataTypes.get(j);
                    ColumnValueBean<?> value = null;
                    switch (dataType) {
                        case STRING_DATA:
                            value = stringGenerator.generate(StringColumnValueBean.class);
                            break;
                        case DOUBLE_DATA:
                            value = doubleGenerator.generate(DoubleColumnValueBean.class);
                            break;
                    }
                    String stringValue = (value == null || value.getValue() == null) ? " "
                            : value.getValue().toString();
                    stringValue = "".equals(stringValue) ? " " : stringValue;
                    out.write(stringValue);
                    out.write(j == this.numCols - 1 ? "" : ",");
                }
                if (i % this.numCols == 0) {
                    System.out.print(".");
                    charsWritten++;
                }
                if (charsWritten > 80) {
                    System.out.println();
                    charsWritten = 0;
                }
                out.newLine();
            }
            System.out.println("done");
        }

    }

    // TODO: file dimensions should be embedded in the file
    public void getNumRows(String fileName) throws IOException {
        try (BufferedReader bro = new BufferedReader(new FileReader(fileName))) {

            String stringFromFile = bro.readLine();
            while (stringFromFile != null) // end of the file
            {
                this.numRows++;
                stringFromFile = bro.readLine(); // read next line
            }
        }
        this.numRows--; // first row contains the data types of the columns
    }

    public TableDataProvider loadData(final String fileName) throws FileNotFoundException, IOException {

        // need to pass through file first to get matrix dimensions
        getNumRows(fileName);

        Object[][] tableData = null;
        try (BufferedReader bro = new BufferedReader(new FileReader(fileName))) {

            // declare String variable and prime the read
            String stringFromFile = bro.readLine();
            boolean columnDataTypesRead = false;
            List<ColumnDataType> dataTypes = null;
            int curRow = 0;
            System.out.print("Loading data");
            while (stringFromFile != null) // end of the file
            {
                if (!columnDataTypesRead) {
                    columnDataTypesRead = true;
                    dataTypes = parseDataTypes(stringFromFile);
                    this.numCols = dataTypes.size();
                    tableData = new Object[this.numCols][this.numRows];
                } else {
                    if (dataTypes == null || dataTypes.size() == 0)
                        throw new IllegalStateException(
                                "Data description line missing");

                    addRow(tableData, curRow++, dataTypes, stringFromFile);
                }
                stringFromFile = bro.readLine(); // read next line
                if (curRow % 1000 == 0)
                    System.out.print(".");
            }
            System.out.println("done");
        }

        return new TableDataProvider(tableData, this.numCols, this.numRows);
    }

    private void addRow(Object[][] tableData, int curRow,
            List<ColumnDataType> dataTypes, String stringFromFile) {
        StringTokenizer tokenizer = new StringTokenizer(stringFromFile, ",");
        String token;
        for (int i = 0; i < dataTypes.size(); i++) {
            ColumnDataType dataType = dataTypes.get(i);
            token = tokenizer.nextToken().trim();
            switch (dataType) {
                case STRING_DATA:
                    tableData[i][curRow] = token;
                    break;
                case DOUBLE_DATA:
                    tableData[i][curRow] = Double.valueOf(token);
                    break;
            }
        }
    }

    private List<ColumnDataType> parseDataTypes(String stringFromFile) {
        List<ColumnDataType> dataTypes = new ArrayList<ColumnDataType>();
        StringTokenizer tokenizer = new StringTokenizer(stringFromFile, ",");
        int numDataTypes = tokenizer.countTokens();
        String token;
        for (int i = 0; i < numDataTypes; i++) {
            token = tokenizer.nextToken().trim();
            ColumnDataType dataType = ColumnDataType.valueOf(token);
            dataTypes.add(dataType);
        }
        return dataTypes;
    }

    public static class ColumnValueBean<T> {
        private T value;

        public T getValue() {
            return this.value;
        }

        public void setValue(T value) {
            this.value = value;
        }
    }

    public static class DoubleColumnValueBean extends ColumnValueBean<Double> {
        @GenerateDouble(range = 1000)
        private Double value;

        @Override
        public Double getValue() {
            return this.value;
        }

        @Override
        public void setValue(Double value) {
            this.value = value;
        }
    };

    public static class StringColumnValueBean extends ColumnValueBean<String> {
        @DataValueGenerator(UppercaseStringValueGenerator.class)
        private String value;

        @Override
        public String getValue() {
            return this.value;
        }

        @Override
        public void setValue(String value) {
            this.value = value;
        }
    };

    public static void main(String[] args) throws GeneratorException, IOException {
        NatTableDataGenerator dataGenerator;
        String fileName;

        dataGenerator = new NatTableDataGenerator(100, 10000);
        fileName = "c:/temp/100x10K_tableData.txt";
        dataGenerator.persistData(fileName);
        dataGenerator = new NatTableDataGenerator(100, 100000);
        fileName = "c:/temp/100x100K_tableData.txt";
        dataGenerator.persistData(fileName);
        dataGenerator = new NatTableDataGenerator(100, 1000000);
        fileName = "c:/temp/100x1mil_tableData.txt";
        dataGenerator.persistData(fileName);
    }
}
