/*
 * #%L
 * gitools-core
 * %%
 * Copyright (C) 2013 Universitat Pompeu Fabra - Biomedical Genomics group
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program 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.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */
package org.gitools.ui.app.heatmap.drawer;

import org.gitools.heatmap.Heatmap;
import org.gitools.heatmap.HeatmapDimension;
import org.gitools.heatmap.HeatmapLayer;
import org.gitools.heatmap.decorator.Decoration;
import org.gitools.heatmap.decorator.Decorator;
import org.gitools.ui.core.Application;
import org.gitools.ui.core.HeatmapPosition;
import org.gitools.ui.platform.progress.JobThread;
import org.gitools.ui.platform.settings.Settings;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

public class HeatmapLayerBodyDrawer extends AbstractHeatmapDrawer {

    private static final AffineTransformOp TRANSFORM_OP = new AffineTransformOp(AffineTransform.getScaleInstance(1, 1), AffineTransformOp.TYPE_BILINEAR);

    private BufferedImage bufferedImage;

    private boolean redrawBufferedImage = true;
    private int svgLimit;

    public HeatmapLayerBodyDrawer(Heatmap heatmap) {
        super(heatmap);

        svgLimit = Settings.get().getSvgBodyLimit();

        PropertyChangeListener redrawImageListener = new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                redrawBufferedImage = true;
            }
        };

        heatmap.getRows().addPropertyChangeListener(HeatmapDimension.PROPERTY_VISIBLE, redrawImageListener);
        heatmap.getColumns().addPropertyChangeListener(HeatmapDimension.PROPERTY_GRID_COLOR, redrawImageListener);
        heatmap.getColumns().addPropertyChangeListener(HeatmapDimension.PROPERTY_VISIBLE, redrawImageListener);
        heatmap.getColumns().addPropertyChangeListener(HeatmapDimension.PROPERTY_GRID_COLOR, redrawImageListener);
        heatmap.getLayers().getTopLayer().getDecorator().addPropertyChangeListener(redrawImageListener);
        heatmap.getLayers().addPropertyChangeListener(redrawImageListener);


    }

    @Override
    public void draw(Graphics2D g, Rectangle box, Rectangle clip) {


        int rowsGridSize = (rows.showGrid() ? rows.getGridSize() : 0);
        int columnsGridSize = (columns.showGrid() ? columns.getGridSize() : 0);


        int cellWidth = columns.getCellSize() + columnsGridSize;
        int cellHeight = rows.getCellSize() + rowsGridSize;

        int rowStart = (clip.y - box.y) / cellHeight;
        int rowEnd = (clip.y - box.y + clip.height + cellHeight - 1) / cellHeight;
        rowEnd = rowEnd < rows.size() ? rowEnd : rows.size();

        int colStart = (clip.x - box.x) / cellWidth;
        int colEnd = (clip.x - box.x + clip.width + cellWidth - 1) / cellWidth;
        colEnd = colEnd < columns.size() ? colEnd : columns.size();

        if (isPictureMode() && ((columns.size() * rows.size()) <= svgLimit)) {
            redraw(g, box, rowsGridSize, columnsGridSize, cellWidth, cellHeight, rowStart, rowEnd, colStart, colEnd);
            return;
        }

        OnScreenRect newRect = new OnScreenRect(rowStart, rowEnd, colStart, colEnd, box.width, box.height, heatmap);

        //clip is sometimes corrupted : too small (when going through the menus)
        boolean corruptedClip = newRect.within(getOnScreenRect());

        redrawBufferedImage = !JobThread.isRunning() && (redrawBufferedImage || !newRect.equals(getOnScreenRect()));

        if (redrawBufferedImage || isPictureMode()) {

            Application.get().setCursorWaiting();

            bufferedImage = new BufferedImage(clip.width, clip.height, BufferedImage.TYPE_4BYTE_ABGR);
            Graphics2D gb = bufferedImage.createGraphics();
            gb.setRenderingHint(
                    RenderingHints.KEY_TEXT_ANTIALIASING,
                    RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

            // Clear background
            gb.setColor(Color.WHITE);
            gb.fillRect(0, 0, clip.width, clip.height);


            redraw(gb, box, rowsGridSize, columnsGridSize, cellWidth, cellHeight, rowStart, rowEnd, colStart, colEnd);

            Application.get().setCursorNormal();

            setOnScreenRect(newRect);
            redrawBufferedImage = false;

        }

        // offset for first
        g.drawImage(bufferedImage, TRANSFORM_OP, colStart * cellWidth, rowStart * cellHeight);


    }

    private void redraw(Graphics2D g, Rectangle box, int rowsGridSize, int columnsGridSize, int cellWidth, int cellHeight, int rowStart, int rowEnd, int colStart, int colEnd) {

        g.setFont(heatmap.getLayers().getTopLayer().getFont());
        calculateFontSize(g, rows.getCellSize(), 7);

        Decoration decoration = new Decoration();
        int y = 0;
        for (int row = rowStart; row < rowEnd; row++) {
            int x = 0;
            for (int col = colStart; col < colEnd; col++) {

                if (heatmap.isDiagonal() && col < row) {
                    decoration.setBgColor(Color.WHITE);
                    decoration.setValue("");
                } else {
                    decorateCell(decoration.reset(), row, col);
                }

                Color rowsGridColor = rows.getGridColor();
                Color colsGridColor = columns.getGridColor();

                g.setColor(colsGridColor);
                g.fillRect(x - columnsGridSize, y, columnsGridSize, cellHeight);

                paintCell(decoration, rowsGridColor, rowsGridSize, x, y, cellWidth - columnsGridSize, cellHeight - rowsGridSize, g, box);

                x += cellWidth;
            }
            y += cellHeight;
        }


    }

    protected void decorateCell(Decoration decoration, int row, int col) {

        HeatmapLayer topLayer = heatmap.getLayers().getTopLayer();
        Decorator decorator = topLayer.getDecorator();

        String rowId = heatmap.getRows().getLabel(row);
        String columnId = heatmap.getColumns().getLabel(col);

        decorator.decorate(decoration, topLayer.getShortFormatter(), heatmap, topLayer, rowId, columnId);
    }


    @Override
    public Dimension getSize() {
        return new Dimension(columns.getFullCellSize() * columns.size(), rows.getFullCellSize() * rows.size());
    }


    @Override
    public HeatmapPosition getPosition(Point p) {

        int cellHeight = rows.getFullCellSize();
        int rowCount = rows.size();
        int totalHeight = cellHeight * rowCount;

        int cellWidth = columns.getFullCellSize();
        int columnCount = columns.size();
        int totalWidth = cellWidth * columnCount;

        int row = p.y >= 0 && p.y < totalHeight ? p.y / cellHeight : -1;
        int column = p.x >= 0 && p.x < totalWidth ? p.x / cellWidth : -1;

        return new HeatmapPosition(heatmap, row, column);
    }


    @Override
    public Point getPoint(HeatmapPosition p) {

        int cellHeight = rows.getFullCellSize();
        int rowCount = rows.size();
        int totalHeight = cellHeight * rowCount;

        int cellWidth = columns.getFullCellSize();
        int columnCount = columns.size();
        int totalWidth = cellWidth * columnCount;

        int x = p.column >= 0 ? p.column * cellWidth : 0;
        if (x > totalWidth) {
            x = totalWidth;
        }

        int y = p.row >= 0 ? p.row * cellHeight : 0;
        if (y > totalHeight) {
            y = totalHeight;
        }

        return new Point(x, y);
    }
}
