/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2011-2018 Broad Institute, Aiden Lab
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */

package juicebox.windowui;

import juicebox.Context;
import juicebox.HiC;
import juicebox.HiCGlobals;
import juicebox.data.ChromosomeHandler;
import juicebox.data.MatrixZoomData;
import juicebox.track.HiCGridAxis;
import org.broad.igv.feature.Chromosome;

import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.io.Serializable;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.NumberFormat;

/**
 * @author jrobinso
 */
public class HiCRulerPanel extends JPanel implements Serializable {

    private static final long serialVersionUID = 3754386054158787331L;
    private static boolean showOnlyEndPts = false;
    private static boolean showChromosomeFigure = true;
    private final Font tickFont = HiCGlobals.font(9, false);
    private final Font spanFont = HiCGlobals.font(12, false);
    private final HiC hic;
    private Orientation orientation;
    private Context context;

    public HiCRulerPanel(HiC hic) {
        this.hic = hic;
        if (HiCGlobals.isDarkulaModeEnabled) {
            setBackground(Color.BLACK);
        } else {
            setBackground(Color.WHITE);
        }
    }

    private static String formatNumber(double position) {

        if (showOnlyEndPts) {
            //Export Version
            NumberFormat df = NumberFormat.getInstance();
            df.setMinimumFractionDigits(2);
            df.setMaximumFractionDigits(2);
            df.setRoundingMode(RoundingMode.DOWN);
            //return f.valueToString(position);
            return df.format(position);
        } else {
            DecimalFormat formatter = new DecimalFormat();
            return formatter.format((int) position);
        }
    }

    private static TickSpacing findSpacing(long maxValue, int width, boolean scaleInKB) {

        if (maxValue < 10) {
            return new TickSpacing(1, HiC.Unit.BP.toString(), 1);
        }

        int maxNumberOfTickMarks = (int) Math.ceil(width / 25);

        // How many zeroes?
        int nZeroes = (int) Math.log10(maxValue);
        String majorUnit = scaleInKB ? "KB" : HiC.Unit.BP.toString();
        int unitMultiplier = 1;
        if (nZeroes > 9) {
            majorUnit = scaleInKB ? "TB" : "GB";
            unitMultiplier = (int) 1e9;
        }
        if (nZeroes > 6) {
            majorUnit = scaleInKB ? "GB" : "MB";
            unitMultiplier = (int) 1e6;
        } else if (nZeroes > 3) {
            majorUnit = scaleInKB ? "MB" : "KB";
            unitMultiplier = 1000;
        }

        int decrementIter = nZeroes - 1;
        while (decrementIter > -1) {

            int latestIncrement = (int) Math.pow(10, nZeroes - decrementIter);
            int nMajorTicks = (int) Math.ceil(maxValue / latestIncrement);

            if (nMajorTicks < maxNumberOfTickMarks) {
                return new TickSpacing(latestIncrement, majorUnit, unitMultiplier);
            }

            latestIncrement = (int) Math.pow(10, nZeroes - decrementIter + 1) / 2;
            nMajorTicks = (int) Math.ceil(maxValue / latestIncrement);

            if (nMajorTicks < maxNumberOfTickMarks) {
                return new TickSpacing(latestIncrement, majorUnit, unitMultiplier);
            }

            decrementIter--;
        }

        int spacing = (int) (maxValue / maxNumberOfTickMarks) / 250 * 250;

        return new TickSpacing(spacing, majorUnit, unitMultiplier);
    }

    public static boolean getShowOnlyEndPts() {
        return showOnlyEndPts;
    }

    public static void setShowOnlyEndPts(boolean toggled) {
        showOnlyEndPts = toggled;
    }

    public static boolean getShowChromosomeFigure() {
        return showChromosomeFigure;
    }

    public static void setShowChromosomeFigure(boolean toggled) {
        showChromosomeFigure = toggled;
    }

    public void setContext(Context frame, Orientation orientation) {
        this.context = frame;
        this.orientation = orientation;
    }

    @Override
    protected void paintComponent(Graphics g) {

        super.paintComponent(g);

        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        Graphics2D g2D = (Graphics2D) g;

        try {
            hic.getZd();
        } catch (Exception e) {
            return;
        }

        if (context == null) return;

        g.setColor(Color.black);

        AffineTransform t = g2D.getTransform();

        if (orientation == Orientation.VERTICAL) {
            AffineTransform rotateTransform = new AffineTransform();
            rotateTransform.quadrantRotate(-1);
            g2D.transform(rotateTransform);
        }

        // Clear panel
        drawTicks(g2D); // TODO ms3
        drawChr(g2D);

        g2D.setTransform(t);


    }

    private void drawChr(Graphics g) {
        int w = isHorizontal() ? getWidth() : getHeight();
        int h = isHorizontal() ? getHeight() : getWidth();

        g.setFont(spanFont);

        Chromosome chromosome = context.getChromosome();

        if (chromosome != null) {
            if (!ChromosomeHandler.isAllByAll(chromosome)) {
                String rangeString = chromosome.getName();
                int strWidth = g.getFontMetrics().stringWidth(rangeString);
                int strPosition = (w - strWidth) / 2;

                if (!isHorizontal()) strPosition = -strPosition;

                if (hic.isVSTypeDisplay()) {
                    if (isHorizontal()) {
                        rangeString = rangeString + " (control)";
                    } else {
                        rangeString = rangeString + " (observed)";
                    }
                }

                int vPos = h - 35;

                if (!showChromosomeFigure) {
                    g.drawString(rangeString, strPosition, vPos);
                }

            }
        }
    }

    private boolean isHorizontal() {
        return orientation == Orientation.HORIZONTAL;
    }

    private void drawTicks(Graphics g) {

        int w = isHorizontal() ? getWidth() : getHeight();
        int h = isHorizontal() ? getHeight() : getWidth();

        Color topTick = new Color(0, 0, 255);
        Color leftTick = new Color(0, 128, 0);


        if (w < 50 || hic.getScaleFactor() == 0) {
            return;
        }

        g.setFont(tickFont);

        Chromosome chromosome = context.getChromosome();

        if (chromosome == null) return;

        MatrixZoomData zd;
        try {
            zd = hic.getZd();
        } catch (Exception e) {
            return;
        }
        if (zd == null || zd.getXGridAxis() == null || zd.getYGridAxis() == null) return;

        if (ChromosomeHandler.isAllByAll(chromosome)) {

            Point cursorPoint = hic.getGWCursorPoint();
            if (cursorPoint != null) {
                int x1 = 0;
                ChromosomeHandler handler = hic.getChromosomeHandler();
                // Index 0 is whole genome
                int genomeCoord = 0;
                for (int i = 1; i < handler.size(); i++) {
                    Color tColor = isHorizontal() ? topTick : leftTick;
                    g.setColor(tColor);

                    double binOrigin = context.getBinOrigin();
                    Chromosome c = handler.getChromosomeFromIndex(i);
                    genomeCoord += (c.getLength() / 1000);

                    int xBin = zd.getXGridAxis().getBinNumberForGenomicPosition(genomeCoord);
                    int x2 = (int) ((xBin - binOrigin) * hic.getScaleFactor());

                    int x = (x1 + x2) / 2;
                    int strWidth = g.getFontMetrics().stringWidth(c.getName());
                    if (isHorizontal()) {
                        if (x1 < cursorPoint.x && cursorPoint.x < x2) {
                            int strPosition = x - strWidth / 2;
                            g.drawString(c.getName(), strPosition, h - 15);
                            g.drawLine(x2, h - 10, x2, h - 2);
                            g.drawLine(x1, h - 10, x1, h - 2);
                            break;
                        }
                    } else {
                        if (-x2 < -cursorPoint.y && -cursorPoint.y < -x1) {
                            int strPosition = -x - strWidth / 2;
                            g.drawString(c.getName(), strPosition, h - 15);
                            g.drawLine(-x2, h - 10, -x2, h - 2);
                            g.drawLine(-x1, h - 10, -x1, h - 2);
                            break;
                        }
                    }
                    x1 = x2;
                }
            }
        } else {
            HiCGridAxis axis = isHorizontal() ? zd.getXGridAxis() : zd.getYGridAxis();

            int binRange = (int) (w / hic.getScaleFactor());
            double binOrigin = context.getBinOrigin();     // <= by definition at left/top of panel

            int genomeOrigin = axis.getGenomicStart(binOrigin);
            int genomeEnd = axis.getGenomicEnd(binOrigin + binRange);
            int range = genomeEnd - genomeOrigin;

            TickSpacing ts = findSpacing(range, w, false);

            if (showOnlyEndPts) {

                // Hundredths decimal point
                int[] genomePositions = hic.getCurrentRegionWindowGenomicPositions();

                double startPosition = isHorizontal() ? genomePositions[0] : genomePositions[2];
                double endPosition = isHorizontal() ? genomePositions[1] : genomePositions[3];
                int endPositionBin = (int) (axis.getBinNumberForGenomicPosition((int) (endPosition - startPosition)) * hic.getScaleFactor());
                
                // actual strings to print and their widths
                String startPositionString = formatNumber(startPosition / ts.getUnitMultiplier()) + " " + ts.getMajorUnit();

                String endPositionString = formatNumber(endPosition / ts.getUnitMultiplier()) + " " + ts.getMajorUnit();
                int startPositionStringWidth = g.getFontMetrics().stringWidth(startPositionString);
                int endPositionStringWidth = g.getFontMetrics().stringWidth(endPositionString);

                //draw start
                int drawPositionStartString = isHorizontal() ? 0 : -startPositionStringWidth;
                g.drawString(startPositionString, drawPositionStartString, h - 15);
                g.drawLine(0, h - 10, 0, h - 2);

                //draw end
                if (!isHorizontal()) endPositionBin = -endPositionBin;
                int drawPositionEndString = isHorizontal() ? endPositionBin - endPositionStringWidth : endPositionBin;
                g.drawString(endPositionString, drawPositionEndString, h - 15);
                g.drawLine(endPositionBin, h - 10, endPositionBin, h - 2);

            } else {
                try {

                    int maxX = context.getChromosome().getLength();
                    double spacing = ts.getMajorTick();

                    // Find starting point closest to the current origin
                    int nTick = (int) (genomeOrigin / spacing) - 1;
                    int genomePosition = (int) (nTick * spacing);

                    int binNumber = axis.getBinNumberForGenomicPosition(genomePosition);
                    int x = (int) ((binNumber - binOrigin) * hic.getScaleFactor());

                    while (genomePosition < maxX && x < w) {
                        Color tColor = isHorizontal() ? topTick : leftTick;
                        g.setColor(tColor);

                        genomePosition = (int) (nTick * spacing);
                        binNumber = axis.getBinNumberForGenomicPosition(genomePosition);
                        x = (int) ((binNumber - binOrigin) * hic.getScaleFactor());

                        String chrPosition = formatNumber((double) genomePosition / ts.getUnitMultiplier() * HiCGlobals.hicMapScale) + " " + ts.getMajorUnit();
                        int strWidth = g.getFontMetrics().stringWidth(chrPosition);
                        int strPosition = isHorizontal() ? x - strWidth / 2 : -x - strWidth / 2;

                        // prevent cut off near origin
                        if (isHorizontal()) {
                            if (binNumber == 0 && strPosition <= 0 && strPosition >= -strWidth / 2)
                                strPosition = 0;
                        } else {
                            if (binNumber == 0 && strPosition >= -strWidth && strPosition <= -strWidth / 2)
                                strPosition = -strWidth;
                        }

                        // todo bug or expected behavior?
                        // see chr1 of k562 mapq30 at fragment resolution
                        // axis is drawing overlapping positions onto each other
                        // traces to getFragmentNumberForGenomicPosition method
                        //System.out.println(genomePosition+"_"+chrPosition+"_"+strPosition);
                        if (nTick % 2 == 0) g.drawString(chrPosition, strPosition, h - 15);

                        int xpos = isHorizontal() ? x : -x;
                        g.drawLine(xpos, h - 10, xpos, h - 2);
                        nTick++;
                    }
                    /* //for previously selected
                    g.setColor(new Color(200,0,0));
                    for(Feature2D feature2D:hic.getSuperAdaptor().getPreviousTempSelectedGroup()){ //add tick marks for previously selected group
                        int startCoordinate = axis.getBinNumberForGenomicPosition(feature2D.getStart1());
                        int endCoordinate = axis.getBinNumberForGenomicPosition(feature2D.getEnd1());
                        startCoordinate = (int) ((startCoordinate - binOrigin) * hic.getScaleFactor());
                        endCoordinate = (int) ((endCoordinate - binOrigin) * hic.getScaleFactor());
                        g.drawLine(startCoordinate,h-10,startCoordinate,h-2);
                        g.setColor(new Color(200,100,50));
                        g.drawLine(endCoordinate,h-10,endCoordinate,h-2);

                    }   */
                } catch (Exception ignored) {
                }
            }
        }
    }

    public enum Orientation {HORIZONTAL, VERTICAL}

    static class TickSpacing {

        private final double majorTick;
        private final double minorTick;
        private String majorUnit = "";
        private int unitMultiplier = 1;

        TickSpacing(double majorTick, String majorUnit, int unitMultiplier) {
            this.majorTick = majorTick;
            this.minorTick = majorTick / 10.0;
            this.majorUnit = majorUnit;
            this.unitMultiplier = unitMultiplier;
        }

        double getMajorTick() {
            return majorTick;
        }

        public double getMinorTick() {
            return minorTick;
        }

        String getMajorUnit() {
            return majorUnit;
        }

        public void setMajorUnit(String majorUnit) {
            this.majorUnit = majorUnit;
        }

        int getUnitMultiplier() {
            return unitMultiplier;
        }

        public void setUnitMultiplier(int unitMultiplier) {
            this.unitMultiplier = unitMultiplier;
        }
    }

// TODO -- possibly generalize?

    class ClickLink {

        final Rectangle region;
        final String value;
        final String tooltipText;

        ClickLink(Rectangle region, String value, String tooltipText) {
            this.region = region;
            this.value = value;
            this.tooltipText = tooltipText;
        }
    }
}