/*******************************************************************************
 * 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.painter.cell;

import java.util.Map;
import java.util.WeakHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;

public class TextPainterOptimisationHarness {

    public static final String EMPTY = "";
    public static final String DOT = "...";

    private static Map<String, Integer> temporaryMap = new WeakHashMap<String, Integer>();
    private static Map<org.eclipse.swt.graphics.Font, FontData[]> fontDataCache = new WeakHashMap<org.eclipse.swt.graphics.Font, FontData[]>();

    private static boolean wrapText = false;

    private static final Pattern endOfPreviousWordPattern = Pattern
            .compile("\\S\\s+\\S+\\s*$");

    private static int expensiveMethodCallCounter = 0;
    private static boolean useOptimiser = true;

    // you can adjust the width of the label and the label text here!!
    private static final int labelWidth = 50;
    private static final String labelMessage = "This is the text to fit into the label";

    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        Display display = new Display();
        Shell shell = new Shell(display);
        Label label = new Label(shell, SWT.BORDER);

        GC gc = new GC(label);
        FontMetrics fm = gc.getFontMetrics();

        int height = fm.getHeight();
        label.setSize(label.computeSize(labelWidth, height));

        useOptimiser = true;
        expensiveMethodCallCounter = 0;
        String textUsingOptimiser = getAvailableTextToDisplay(gc,
                label.getBounds(), labelMessage);
        System.out
                .println("number of expensive method calls when optimised     = "
                        + expensiveMethodCallCounter);

        useOptimiser = false;
        expensiveMethodCallCounter = 0;
        String textWithoutUsingOptimiser = getAvailableTextToDisplay(gc,
                label.getBounds(), labelMessage);
        System.out
                .println("number of expensive method calls when not optimised = "
                        + expensiveMethodCallCounter);

        if (!textWithoutUsingOptimiser.equals(textUsingOptimiser)) {
            throw new Exception("The end result strings were not consistent");
        }

        label.setText(textUsingOptimiser);

        gc.dispose();

        shell.pack();
        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch())
                display.sleep();
        }
        display.dispose();

    }

    private static int getWidthFromCache(GC gc, String text) {
        expensiveMethodCallCounter++;
        String originalString = text;
        StringBuilder buffer = new StringBuilder();
        buffer.append(text);
        if (gc.getFont() != null) {
            FontData[] datas = fontDataCache.get(gc.getFont());
            if (datas == null) {
                datas = gc.getFont().getFontData();
                fontDataCache.put(gc.getFont(), datas);
            }
            if (datas != null && datas.length > 0) {
                buffer.append(datas[0].getName());
                buffer.append(",");
                buffer.append(datas[0].getHeight());
                buffer.append(",");
                buffer.append(datas[0].getStyle());
            }
        }
        text = buffer.toString();
        Integer width = temporaryMap.get(text);
        if (width == null) {
            width = Integer.valueOf(gc.textExtent(originalString).x);
            temporaryMap.put(text, width);
        }

        return width.intValue();
    }

    private static String getAvailableTextToDisplay(GC gc, Rectangle bounds,
            String text) {
        StringBuilder output = new StringBuilder();

        text = text.trim();

        while (text.length() > 0) {
            String line;
            int nextLineBreakIndex;

            int indexOfNewline = text.indexOf('\n');
            if (indexOfNewline > 0) {
                nextLineBreakIndex = indexOfNewline;
                line = text.substring(0, nextLineBreakIndex);
            } else {
                nextLineBreakIndex = -1;
                line = text;
            }

            int textWidth = getWidthFromCache(gc, line);

            if (wrapText) {
                while (textWidth > bounds.width + 1) {
                    Matcher matcher = endOfPreviousWordPattern.matcher(line);
                    if (matcher.find()) {
                        nextLineBreakIndex = matcher.start() + 1;
                        line = line.substring(0, nextLineBreakIndex);
                        textWidth = getWidthFromCache(gc, line);
                    } else {
                        nextLineBreakIndex = -1;
                        break;
                    }
                }
            }

            if (textWidth > bounds.width + 1) {

                expensiveMethodCallCounter = 0;

                if (useOptimiser) {

                    // if we reached this bit of the code, we know we are going
                    // to have to truncate the string

                    String nextTrialString = line;
                    int numExtraChars = 0;
                    int newStringLength = nextTrialString.length()
                            - numExtraChars;

                    String trialLabelText = nextTrialString + DOT;
                    int newTextExtent = getWidthFromCache(gc, trialLabelText);

                    while (newTextExtent > bounds.width + 1
                            && newStringLength > 0) {

                        int avgWidthPerChar = newTextExtent
                                / trialLabelText.length();
                        numExtraChars = 1 + (newTextExtent - bounds.width)
                                / avgWidthPerChar;

                        newStringLength = nextTrialString.length()
                                - numExtraChars;
                        if (newStringLength > 0) {
                            nextTrialString = nextTrialString.substring(0,
                                    newStringLength);
                            trialLabelText = nextTrialString + DOT;
                            newTextExtent = getWidthFromCache(gc,
                                    trialLabelText);
                        }
                    }

                    if (numExtraChars > line.length()) {
                        numExtraChars = line.length();
                    }

                    // now we have gone too short, lets add chars one at a time
                    // to exceed the width...
                    String testString = line;
                    for (int i = 0; i < line.length(); i++) {
                        testString = line.substring(0, line.length() + i
                                - numExtraChars)
                                + DOT;
                        textWidth = getWidthFromCache(gc, testString);

                        if (textWidth >= bounds.width) {

                            // now roll back one as this was the first number
                            // that exceeded
                            if (line.length() + i - numExtraChars < 1) {
                                line = EMPTY;
                            } else {
                                line = line.substring(0, line.length() + i
                                        - numExtraChars - 1)
                                        + DOT;
                            }
                            break;
                        }
                    }
                } else {

                    // this is the expensive non-optimised codebase
                    int textLen = line.length();
                    for (int i = textLen - 1; i >= 0; i--) {
                        String temp = line.substring(0, i) + DOT;

                        textWidth = getWidthFromCache(gc, temp);
                        if (textWidth < bounds.width) {
                            line = temp;
                            break;
                        } else if (i == 0) {
                            line = EMPTY;
                        }
                    }
                }
            }

            output.append(line);

            if (nextLineBreakIndex > 0) {
                text = text.substring(nextLineBreakIndex).trim();

                if (text.length() > 0) {
                    output.append("\n");
                }
            } else {
                break;
            }
        }

        return output.toString();
    }

}
