/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.modules.bugtracking.issuetable;

import java.util.regex.Matcher;
import java.awt.Color;
import java.awt.Component;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.text.MessageFormat;
import java.util.logging.Level;
import java.util.regex.Pattern;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.table.DefaultTableCellRenderer;
import org.netbeans.modules.bugtracking.commons.TextUtils;
import org.netbeans.modules.bugtracking.commons.UIUtils;
import org.netbeans.modules.bugtracking.issuetable.IssueNode.IssueProperty;
import org.netbeans.modules.bugtracking.spi.IssueStatusProvider;
import static org.netbeans.modules.bugtracking.spi.IssueStatusProvider.Status.INCOMING_MODIFIED;
import static org.netbeans.modules.bugtracking.spi.IssueStatusProvider.Status.INCOMING_NEW;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;

/**
 *
 * @author Tomas Stupka
 */
public class QueryTableCellRenderer extends DefaultTableCellRenderer {
    public static final String PROPERTY_FORMAT = "format";                      // NOI18N
    public static final String PROPERTY_HIGHLIGHT_PATTERN = "highlightPattern"; // NOI18N

    private final IssueTable issueTable;

    private static final int VISIBLE_START_CHARS = 0;
    private static final Icon seenValueIcon = new ImageIcon(ImageUtilities.loadImage("org/netbeans/modules/bugtracking/commons/resources/seen-value.png")); // NOI18N

    private static final MessageFormat issueNewFormat       = getFormat("issueNewFormat", UIUtils.getTaskNewColor()); //NOI18N
    private static final MessageFormat issueObsoleteFormat  = getFormat("issueObsoleteFormat", UIUtils.getTaskObsoleteColor()); //NOI18N
    private static final MessageFormat issueModifiedFormat  = getFormat("issueModifiedFormat", UIUtils.getTaskModifiedColor()); //NOI18N

    private static final String labelNew = NbBundle.getMessage(QueryTableCellRenderer.class, "LBL_IssueStatusNew");             // NOI18N
    private static final String labelModified = NbBundle.getMessage(QueryTableCellRenderer.class, "LBL_IssueStatusModified");   // NOI18N
    private static final String labelObsolete = NbBundle.getMessage(QueryTableCellRenderer.class, "LBL_IssueStatusObsolete");   // NOI18N

    private static final String msgNew = NbBundle.getMessage(QueryTableCellRenderer.class, "MSG_IssueStatusNew");             // NOI18N
    private static final String msgModified = NbBundle.getMessage(QueryTableCellRenderer.class, "MSG_IssueStatusModified");   // NOI18N
    private static final String msgObsolete = NbBundle.getMessage(QueryTableCellRenderer.class, "MSG_IssueStatusObsolete");   // NOI18N

    private static Color evenLineColor                      = null;
    private static Color unevenLineColor                    = null;
    private static final Color newHighlightColor            = UIUtils.getTaskNewColor();
    private static final Color modifiedHighlightColor       = UIUtils.getTaskModifiedColor();
    private static final Color obsoleteHighlightColor       = UIUtils.getTaskObsoleteColor();

    static {
        evenLineColor = UIManager.getColor( "nb.bugtracking.table.background" ); //NOI18N
        unevenLineColor = UIManager.getColor( "nb.bugtracking.table.background.alternate" ); //NOI18N
        if (evenLineColor == null || unevenLineColor == null) {
            Color textColor = UIManager.getColor("Table.foreground"); // NOI18N
            boolean textOnBright = textColor == null || (textColor.getRed() < 192 && textColor.getGreen() < 192 && textColor.getBlue() < 192);
            if (evenLineColor == null) {
                evenLineColor = UIManager.getColor("Table.background"); // NOI18N
                if (evenLineColor == null) {
                    evenLineColor = textOnBright ? Color.white : Color.black;
                }
            }
            if (unevenLineColor == null) {
                unevenLineColor = textOnBright ? new Color(0xf3f6fd) : Color.darkGray;
            }
        }
    }

    private boolean isSaved;

    public QueryTableCellRenderer(IssueTable issueTable, boolean isSaved) {
        this.issueTable = issueTable;
        this.isSaved = isSaved;
    }

    void setSaved(boolean saved) {
        isSaved = saved;
    }
    
    private static MessageFormat getFormat (String key, Color c) {
        String format = NbBundle.getMessage(IssueTable.class, key,
                new Object[] { UIUtils.getColorString(c), "{0}" }); //NOI18N
        return new MessageFormat(format);
    }

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {

        JLabel renderer = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
        renderer.setIcon(null);
        if(!isSaved) {
            TableCellStyle style = getDefaultCellStyle(table, issueTable, (IssueProperty) value, isSelected, row);
            setStyleProperties(renderer, style);
            return renderer;
        }
        
        TableCellStyle style = null;
        if(value instanceof IssueNode.SeenProperty) {
            IssueNode.SeenProperty ps = (IssueNode.SeenProperty) value;
            renderer.setIcon(!ps.getValue() ? seenValueIcon : null);
            renderer.setText("");                                               // NOI18N
        } 

        if(value instanceof IssueNode.IssueProperty) {
            style = getCellStyle(table, issueTable, (IssueProperty)value, isSelected, row);
        }
        setStyleProperties(renderer, style);
        return renderer;
    }

    public void setStyleProperties(JLabel renderer, TableCellStyle style) {
        if (style != null) {
            renderer.putClientProperty(PROPERTY_FORMAT, style.format); // NOI18N
            renderer.putClientProperty(PROPERTY_HIGHLIGHT_PATTERN, style.highlightPattern); // NOI18N
            ((JComponent) renderer).setToolTipText(style.tooltip);
            setRowColors(style, renderer);
        }
    }

    @Override
    protected void paintComponent(Graphics g) {        
        processText(this);
        super.paintComponent(g);
    }

    public static void processText(JLabel label) {
        MessageFormat format = (MessageFormat) label.getClientProperty(PROPERTY_FORMAT);     // NOI18N
        Pattern pattern = (Pattern) label.getClientProperty(PROPERTY_HIGHLIGHT_PATTERN);     // NOI18N
        String s = computeFitText(label);
        if(format != null || pattern != null) {
            StringBuffer sb = new StringBuffer();
            sb.append("<html>");                                                // NOI18N
            s = TextUtils.escapeForHTMLLabel(s);
            if(format != null) {
                format.format(new Object[] {s}, sb, null);
            }
            if(pattern != null) {
                sb.append(highLight(s, pattern));
            }
            sb.append("</html>");                                               // NOI18N
            s = sb.toString();
        } 
        label.setText(s);
    }

    private static String computeFitText(JLabel label) {
        String text = label.getText();
        if(text == null) text = "";
        if (text.length() <= VISIBLE_START_CHARS + 3) return text;
        
        Icon icon = label.getIcon();
        int iconWidth = icon != null ? icon.getIconWidth() : 0;
        
        FontMetrics fm = label.getFontMetrics(label.getFont());
        int width = label.getSize().width - iconWidth;

        String sufix = "...";                                                   // NOI18N
        int sufixLength = fm.stringWidth(sufix);
        int desired = width - sufixLength;
        if (desired <= 0) return text;

        for (int i = 0; i <= text.length() - 1; i++) {
            String prefix = text.substring(0, i);
            int swidth = fm.stringWidth(prefix);
            if (swidth >= desired) {
                return prefix.length() > 0 ? prefix + sufix: text;
            }
        }
        return text;
    }

    private static String highLight(String s, Pattern pattern) {
        Matcher matcher = pattern.matcher(s);
        int idx = 0;
        StringBuilder sb = new StringBuilder();
        while (matcher.find(idx)) {
            int start = matcher.start();
            int end = matcher.end();
            if (start == end) {
                break;
            }
            sb.append(s.substring(idx, start));
            sb.append("<font bgcolor=\"FFB442\" color=\"black\">");
            sb.append(s.substring(start, end));
            sb.append("</font>");
            idx = matcher.end();
        }
        if(sb.length() > 0) {
            sb.append(idx < s.length() ? s.substring(idx, s.length()) : "");
            s = sb.toString();
        }
        return s;
    }

    public static class TableCellStyle {
        private MessageFormat format;
        private Color background;
        private Color foreground;
        private String tooltip;
        private Pattern highlightPattern;

        private TableCellStyle(MessageFormat format, Color background, Color foreground, String tooltip, Pattern highlightPattern) {
            this.background = background;
            this.foreground = foreground;
            this.tooltip = tooltip;
            this.format = format;
            this.highlightPattern = highlightPattern;
        }
        public Color getBackground() {
            return background;
        }
        public Color getForeground() {
            return foreground;
        }
        public MessageFormat getFormat() {
            return format;
        }
        public Pattern getHighlightPattern() {
            return highlightPattern;
        }
        public String getTooltip() {
            return tooltip;
        }
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("[");                                                     // NOI18N
            sb.append("background=");                                           // NOI18N
            sb.append(background);
            sb.append(", foreground=");                                         // NOI18N
            sb.append(foreground);
            sb.append(", format=");                                             // NOI18N
            sb.append(format != null ? format.toPattern() : null);
            sb.append(", tooltip=");                                            // NOI18N
            sb.append(tooltip);
            sb.append("]");                                                     // NOI18N
            return sb.toString();
        }
        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final TableCellStyle other = (TableCellStyle) obj;
            if (this.format != other.format && (this.format == null || !this.format.equals(other.format))) {
                return false;
            }
            if (this.background != other.background && (this.background == null || !this.background.equals(other.background))) {
                return false;
            }
            if (this.foreground != other.foreground && (this.foreground == null || !this.foreground.equals(other.foreground))) {
                return false;
            }
            if ((this.tooltip == null) ? (other.tooltip != null) : !this.tooltip.equals(other.tooltip)) {
                return false;
            }
            return true;
        }
        @Override
        public int hashCode() {
            return toString().hashCode();
        }

    }
    
    public static TableCellStyle getCellStyle(JTable table, IssueTable issueTable, IssueProperty p, boolean isSelected, int row) {
        TableCellStyle style = getDefaultCellStyle(table, issueTable, p, isSelected, row);
        try {
            // set text format and background depending on selection and issue status
            IssueStatusProvider.Status status = p.getStatus();
            if(status != IssueStatusProvider.Status.SEEN) {
                switch(status) {
                    case INCOMING_NEW :
                        style.format     = isSelected ? style.format      : issueNewFormat;
                        style.background = isSelected ? newHighlightColor : style.background;
                        style.foreground = isSelected ? table.getBackground() : style.foreground;
                        break;
                    case INCOMING_MODIFIED :
                        style.format     = isSelected ? style.format           : issueModifiedFormat;
                        style.background = isSelected ? modifiedHighlightColor : style.background;
                        style.foreground = isSelected ? table.getBackground() : style.foreground;
                        break;
                }
            }
            
            Object o = p.getValue();
            if(o instanceof String) {
                String s = (String) o;
                if(s == null) {
                    s = "";
                }                                               // NOI18N
                s = TextUtils.escapeForHTMLLabel(s);
                StringBuilder sb = new StringBuilder();
                sb.append("<html>");                                                // NOI18N
                sb.append(s);
                if(status == null) {
                    status = p.getStatus();
                }
                switch(status) {
                    case INCOMING_NEW :
                        sb.append("<br>").append(issueNewFormat.format(new Object[] { labelNew }, new StringBuffer(), null)); // NOI18N
                        sb.append(msgNew);
                        break;
                    case INCOMING_MODIFIED :
                        sb.append("<br>").append(issueModifiedFormat.format(new Object[] { labelModified }, new StringBuffer(), null)); // NOI18N
                        sb.append(MessageFormat.format(msgModified, p.getRecentChanges()));
                        break;
                }
                sb.append("</html>"); // NOI18N
                style.tooltip = sb.toString();
            }
        } catch (Exception ex) {
            IssueTable.LOG.log(Level.WARNING, null, ex);
        }
        return style;
    }
    public static TableCellStyle getDefaultCellStyle(JTable table, IssueTable issueTable, IssueProperty p, boolean isSelected, int row) {
        // set default values
        return new TableCellStyle(
            null,                                                                       // format
            isSelected ? table.getSelectionBackground() : getUnselectedBackground(row), // background
            isSelected ? Color.WHITE : table.getForeground(),                           // foreground
            null,                                                                       // tooltip
            getHightlightPattern(issueTable, p)
        );
    }

    private static Pattern getHightlightPattern(IssueTable issueTable, IssueProperty p) {
        if(p instanceof IssueNode.SummaryProperty) {            
            SummaryTextFilter f = issueTable.getSummaryFilter();
            if(f != null && f.isHighLightingOn()) {
                return f.getPattern();
            }
        }
        return null;
    }

    private static Color getUnselectedBackground(int row) {
        return row % 2 != 0 ? unevenLineColor : evenLineColor;
    }

    public static void setRowColors(TableCellStyle style, JComponent l) {
        if(style == null) {
            assert false;
            return; // prefer to do nothing instead of breaking the rendering with an NPE
        }
        if (style.background != null) {
            l.setBackground(style.background);
        }
        if (style.foreground != null) {
            l.setForeground(style.foreground);
        }
    }
}


