/**
 * Copyright (c) 2014 by Brainwy Software LTDA. All Rights Reserved.
 * Licensed under the terms of the Eclipse Public License (EPL).
 * Please see the license.txt included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
package org.python.pydev.shared_ui.editor;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.eclipse.core.runtime.Assert;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.python.pydev.shared_core.string.TextSelectionUtils;

public class TextVerticalLinesIndentGuide implements IVerticalLinesIndentGuideComputer {

    private IVerticalIndentGuidePreferencesProvider verticalIndentPrefs;

    public TextVerticalLinesIndentGuide(IVerticalIndentGuidePreferencesProvider verticalIndentPrefs) {
        Assert.isNotNull(verticalIndentPrefs);
        this.verticalIndentPrefs = verticalIndentPrefs;
    }

    @Override
    public int getTabWidth() {
        return verticalIndentPrefs.getTabWidth();
    }

    @Override
    public boolean getShowIndentGuide() {
        return verticalIndentPrefs.getShowIndentGuide();
    }

    @Override
    public Color getColor(StyledText styledText) {
        return verticalIndentPrefs.getColor(styledText);
    }

    @Override
    public int getTransparency() {
        return verticalIndentPrefs.getTransparency();
    }

    @Override
    public void dispose() {
        verticalIndentPrefs.dispose();
    }

    @Override
    public SortedMap<Integer, List<VerticalLinesToDraw>> computeVerticalLinesToDrawInRegion(
            StyledText styledText, int topIndex, int bottomIndex) {
        final int originalTopIndex = topIndex;

        SortedMap<Integer, List<VerticalLinesToDraw>> lineToVerticalLinesToDraw;
        lineToVerticalLinesToDraw = new TreeMap<Integer, List<VerticalLinesToDraw>>();
        int lineHeight = styledText.getLineHeight();
        int lineCount = styledText.getLineCount();
        if (bottomIndex > lineCount - 1) {
            bottomIndex = lineCount - 1;
        }
        // lineHeight = styledText.getLinePixel(1) - styledText.getLinePixel(0);

        // Note: if the top index is an all whitespace line, we have to start computing earlier to have something valid at the all whitespaces line
        while (topIndex > 0) {
            final String string = styledText.getLine(topIndex);
            int firstCharPosition = TextSelectionUtils.getFirstCharPosition(string);
            if (firstCharPosition == string.length()) {
                // All whitespaces... go back until we find one that is not only whitespaces.
                topIndex--;
            } else {
                break;
            }
        }

        for (int line = topIndex; line <= bottomIndex; line++) {
            // Only draw in visible range... (topIndex/bottomIndex)

            final String string = styledText.getLine(line);

            int firstCharPosition = TextSelectionUtils.getFirstCharPosition(string);

            if (firstCharPosition == string.length()) {
                // The line only has whitespaces... Let's copy the indentation guide from the previous line (if any)
                // just updating the y.
                List<VerticalLinesToDraw> previousLine = lineToVerticalLinesToDraw.get(line - 1);

                if (previousLine != null) {
                    ArrayList<VerticalLinesToDraw> newLst = new ArrayList<>(previousLine.size());
                    for (VerticalLinesToDraw verticalLinesToDraw : previousLine) {
                        newLst.add(verticalLinesToDraw.copyChangingYOffset(lineHeight));
                    }
                    lineToVerticalLinesToDraw.put(line, newLst);
                }
                continue;
            }

            if (firstCharPosition == 0) {
                continue;
            }

            computeLine(string, firstCharPosition, styledText, line, lineHeight, lineToVerticalLinesToDraw);
        }
        if (originalTopIndex != topIndex) {
            // Remove the entries we created just because we had to generate based on previous lines (those shouldn't be drawn:
            // we only want the visible region in the return).
            Set<Entry<Integer, List<VerticalLinesToDraw>>> entrySet = lineToVerticalLinesToDraw.entrySet();
            Iterator<Entry<Integer, List<VerticalLinesToDraw>>> iterator = entrySet.iterator();
            while (iterator.hasNext()) {
                Entry<Integer, List<VerticalLinesToDraw>> next = iterator.next();
                if (next.getKey() < originalTopIndex) {
                    iterator.remove();
                } else {
                    break; //As it's sorted, we know we can bail out early.
                }
            }
        }
        return lineToVerticalLinesToDraw;
    }

    private void computeLine(String string, int firstCharPosition, StyledText styledText, int line, int lineHeight,
            SortedMap<Integer, List<VerticalLinesToDraw>> lineToVerticalLinesToDraw) {
        int lineOffset = -1;

        String spaces = string.substring(0, firstCharPosition);
        int level = 0;
        int whitespacesFound = 0;
        int tabWidthUsed = getTabWidth();
        for (int j = 0; j < firstCharPosition - 1; j++) { //-1 because we don't want to cover for the column where a non whitespace char is.
            char c = spaces.charAt(j);
            if (c == '\t') {
                level++;
                whitespacesFound = 0;
            } else {
                //whitespace (not tab)
                whitespacesFound++;
                if (whitespacesFound % tabWidthUsed == 0) {
                    level++;
                    whitespacesFound = 0;
                }
            }
            if (level > 0) {
                Point point1;

                if (lineOffset == -1) {
                    lineOffset = styledText.getOffsetAtLine(line);
                }
                point1 = styledText.getLocationAtOffset(lineOffset + j + 1);
                int xCoord = point1.x + 3;

                VerticalLinesToDraw verticalLinesToDraw = new VerticalLinesToDraw(xCoord,
                        point1.y, xCoord, point1.y + lineHeight);

                List<VerticalLinesToDraw> lst = lineToVerticalLinesToDraw.get(line);
                if (lst == null) {
                    lst = new ArrayList<VerticalLinesToDraw>();
                    lineToVerticalLinesToDraw.put(line, lst);
                }
                lst.add(verticalLinesToDraw);

                level--;
            }
        }
    }

}
