/*
 * PROJECT Mokka7 (fork of Snap7/Moka7)
 *
 * Copyright (c) 2017 J.Zimmermann (comtel2000)
 *
 * All rights reserved. This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Mokka7 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 whatever license you
 * decide to adopt.
 *
 * Contributors: J.Zimmermann - Mokka7 fork
 *
 */
package org.comtel2000.mokka7.client.control;

import java.nio.ByteBuffer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;

public class HexTableView extends TableView<Integer> {

    private final static String DEFAULT_STYLE = HexTableView.class.getResource("hex-table.css").toExternalForm();

    private final static char EMP_CHAR = '\u0020', DOT_CHAR = '.';
    private final static char HEX_DIGIT[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

    private final StringBuilder sb = new StringBuilder();

    private ByteBuffer buffer;

    public HexTableView() {
        super();
        init();
    }

    @SuppressWarnings("unchecked")
    private void init() {
        getStyleClass().add("hex-table-view");
        // setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

        TableColumn<Integer, String> count = new TableColumn<>("OFFSET");
        count.setId("count-column");
        count.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper<String>(createCountItem(cellData.getValue())));

        TableColumn<Integer, String> hex = new TableColumn<>("00 01 02 03 04 05 06 07  08 09 0A 0B 0C 0D 0E 0F");
        hex.setId("hex-column");
        hex.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper<String>(createHexItem(cellData.getValue())));

        TableColumn<Integer, String> text = new TableColumn<>("TEXT");
        text.setId("text-column");
        text.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper<String>(createItem(cellData.getValue())));

        getColumns().addAll(count, hex, text);
        sortPolicyProperty().set(t -> false);
        setColumnResizePolicy((param) -> true);
    }

    @Override
    public String getUserAgentStylesheet() {
        return DEFAULT_STYLE;
    }

    public void setData(byte[] buf) {
        buffer = ByteBuffer.wrap(buf);
        Platform.runLater(() -> initItems(Math.floorDiv(buf.length, 16) + 1));
    }

    public ByteBuffer getData() {
        return buffer;
    }

    private void initItems(int size) {
        getItems().setAll(IntStream.range(0, size).boxed().collect(Collectors.toList()));
    }

    private String createCountItem(int value) {

        int pos = getPosition(value);
        if (pos < 0) {
            return null;
        }
        int length = Math.min(16, buffer.limit() - pos);
        if (length < 1) {
            return null;
        }

        sb.setLength(0);
        for (int i = 16; i >= 0; i -= 4) {
            sb.append(HEX_DIGIT[0x0F & value >>> i]);
        }
        return sb.toString();
    }

    private String createHexItem(int value) {

        int pos = getPosition(value);
        if (pos < 0) {
            return null;
        }
        int length = Math.min(16, buffer.limit() - pos);
        if (length < 1) {
            return null;
        }
        sb.setLength(0);
        for (int i = 0; i < 16; i++) {
            if (i < length) {
                sb.append(HEX_DIGIT[0x0F & buffer.get(pos + i) >> 4]);
                sb.append(HEX_DIGIT[0x0F & buffer.get(pos + i)]);
            } else {
                sb.append(EMP_CHAR).append(EMP_CHAR);
            }
            sb.append(EMP_CHAR);
            if (i == 7) {
                sb.append(EMP_CHAR);
            }
        }
        return sb.toString();
    }

    private String createItem(int value) {

        int pos = getPosition(value);
        if (pos < 0) {
            return null;
        }
        int length = Math.min(16, buffer.limit() - pos);
        if (length < 1) {
            return null;
        }

        sb.setLength(0);
        for (int i = 0; i < 16; i++) {
            if (i >= length) {
                sb.append(EMP_CHAR);
                continue;
            }
            char ch = (char) (buffer.get(pos + i) & 0xFF);
            if (Character.isISOControl(ch) || Character.isWhitespace(ch)) {
                sb.append(DOT_CHAR);
                continue;
            }
            sb.append(ch);
        }
        return sb.toString();
    }

    private int getPosition(int value) {
        if (buffer == null || buffer.limit() == 0) {
            return -1;
        }
        int pos = value * 16;
        if (pos >= buffer.limit()) {
            return -1;
        }
        return pos;
    }
}