/*******************************************************************************
 * Copyright (c) 2005, 2007 IBM Corporation and others.
 * 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
 *
 
 *******************************************************************************/
package org.python.pydev.shared_interactive_console.console.ui;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TypedRegion;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.console.IConsoleDocumentPartitioner;

/**
 * This document partitioner does not actually partition things, but receives ranges with the 
 * valid styles, so, each range maps to a style.
 * 
 * Aside from that, all is marked as the default content type.
 */
public class ScriptConsolePartitioner implements IConsoleDocumentPartitioner {

    private static final String[] LEGAL_CONTENT_TYPES = new String[] { IDocument.DEFAULT_CONTENT_TYPE };

    private List<ScriptStyleRange> ranges = new ArrayList<ScriptStyleRange>();

    public ScriptConsolePartitioner() {
    }

    /**
     * Adds a given style range.
     * 
     * When a range is added, the ranges that were added before must be removed/updated if the added range
     * has some intersection with a previous one.
     * 
     * The ranges must all be set sequentially, so, all the ranges that have some intersection with that 
     * range must be removed/updated.
     * 
     * @param r the range to be added.
     */
    public void addRange(ScriptStyleRange r) {
        if (r.length > 0) { //only add ranges with some len.
            for (int i = ranges.size() - 1; i >= 0; i--) {
                ScriptStyleRange last = ranges.get(i);
                int end = last.start + last.length;
                if (end > r.start) {
                    if (r.start <= last.start) {
                        ranges.remove(i);
                    } else {
                        last.length = r.start - last.start;
                    }
                } else {
                    break;
                }
            }

            boolean updatedRange = false;
            if (ranges.size() > 0) {
                ScriptStyleRange lastRange = ranges.get(ranges.size() - 1);
                if (lastRange.scriptType == r.scriptType && equalsColor(lastRange.foreground, r.foreground)
                        && equalsColor(lastRange.background, r.background)) {
                    if (lastRange.start + lastRange.length == r.start) {
                        lastRange.length += r.length;
                        updatedRange = true;
                    }
                }
            }

            if (!updatedRange) {
                ranges.add(r);
            }
        }
    }

    private boolean equalsColor(Color foreground, Color foreground2) {
        if (foreground == foreground2) {
            return true;
        }
        if (foreground == null || foreground2 == null) {
            return false;
        }

        return foreground.equals(foreground2);
    }

    /**
     * @return the ranges that intersect with the given offset/length.
     */
    public ScriptStyleRange[] getStyleRanges(int offset, int length) {
        int lastOffset = -1;

        boolean found = false;

        List<ScriptStyleRange> result = new ArrayList<ScriptStyleRange>();
        for (int i = ranges.size() - 1; i >= 0; i--) {
            ScriptStyleRange r = ranges.get(i);
            if ((r.start >= offset && r.start <= offset + length) || (r.start < offset && r.start + r.length > offset)) {
                found = true;
                //it must always be a copy because it may be changed later by the TextConsole when
                //some hyperlink has a matching position.
                result.add(0, (ScriptStyleRange) r.clone());
                if (lastOffset == -1) {
                    lastOffset = r.start + r.length;
                }
            } else if (found) {
                //break on 1st not found (after finding the 1st)
                break;
            }
        }

        if (lastOffset == -1) {
            lastOffset = offset;
        }

        if (lastOffset < offset + length) {
            //if we haven't been able to cover the whole range, there's probably something wrong (so, let's 
            //leave it in gray so that we know about that).
            ScriptStyleRange lastPart = new ScriptStyleRange(lastOffset, (offset + length) - lastOffset, Display
                    .getDefault().getSystemColor(SWT.COLOR_GRAY), Display.getDefault().getSystemColor(SWT.COLOR_WHITE),
                    ScriptStyleRange.UNKNOWN);
            result.add(lastPart);
        }

        return (ScriptStyleRange[]) result.toArray(new ScriptStyleRange[result.size()]);
    }

    //-------------------- Just return default content type for any related request ------------------------------------

    public boolean isReadOnly(int offset) {
        return false;
    }

    public ITypedRegion[] computePartitioning(int offset, int length) {
        return new TypedRegion[] { new TypedRegion(offset, length, IDocument.DEFAULT_CONTENT_TYPE) };
    }

    public void connect(IDocument document) {
    }

    public void disconnect() {
    }

    public void documentAboutToBeChanged(DocumentEvent event) {
    }

    public boolean documentChanged(DocumentEvent event) {
        return false;
    }

    public String getContentType(int offset) {
        return IDocument.DEFAULT_CONTENT_TYPE;
    }

    public String[] getLegalContentTypes() {
        return LEGAL_CONTENT_TYPES;
    }

    public ITypedRegion getPartition(int offset) {
        return new TypedRegion(offset, 1, IDocument.DEFAULT_CONTENT_TYPE);
    }
}
