/*
 * jNPad v0.3 - jNPad's an Simple Text Editor written in Java
 *
 * Copyright (C) 2014-2017  rgs
 *
 * Require JDK 1.6 (or later)
 *
 * 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 2 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 *
 * Info, Questions, Suggestions & Bugs Report to rgsevero@gmail.com
 */

package jnpad.text;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.JWindow;
import javax.swing.KeyStroke;
import javax.swing.UIDefaults;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicTextAreaUI;
import javax.swing.text.BadLocationException;
import javax.swing.text.EditorKit;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.View;

import jnpad.config.Config;
import jnpad.config.Configurable;
import jnpad.text.syntax.ASN1View;
import jnpad.text.syntax.CPPView;
import jnpad.text.syntax.CView;
import jnpad.text.syntax.ContentTypes;
import jnpad.text.syntax.JavaView;
import jnpad.text.syntax.PlainView2;
import jnpad.text.syntax.PropertiesView;
import jnpad.text.syntax.WrappedPlainView2;
import jnpad.ui.MiniScrollPane;
import jnpad.ui.plaf.LAFUtils;

/**
 * The Class MinimapWindow.
 *
 * @version 0.3
 * @since   jNPad v0.3
 */
public class MinimapWindow extends JWindow implements Configurable {
  JPanel                    contentPane;
  MiniViewer                miniViewer;

  JPopupMenu                popupMenu        = new JPopupMenu();
  JCheckBoxMenuItem         cbmiShowViewRect = new JCheckBoxMenuItem();
  JMenuItem                 miCloseMinimap   = new JMenuItem();

  private EditPane          editPane;

  /** Logger */
  private static Logger     LOGGER           = Logger.getLogger(MinimapWindow.class.getName());

  /** UID */
  private static final long serialVersionUID = 6762112706921698054L;

  /**
   * Instantiates a new mini viewer window.
   *
   * @param editPane the edit pane
   */
  public MinimapWindow(final EditPane editPane) {
    super(editPane.buffer.jNPad);
    
    this.editPane = editPane;
    
    try {
      miniViewer = new MiniViewer(editPane);
      
      jbInit();
      
      Dimension s = editPane.getSize();
      //s.width /= 1.1;
      s.height /= 2;
      setSize(s);
      setLocation(editPane.getLocationOnScreen());

      setVisible(true);
      
      miniViewer.centerViewport();
      if (cbmiShowViewRect.isSelected())
        miniViewer.textArea.adjustViewRect();
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
    }
  }

  /**
   * Inicialización de componentes.
   * 
   * @throws Exception the exception
   */
  private void jbInit() throws Exception {
    contentPane = (JPanel) this.getContentPane();
    contentPane.setLayout(new BorderLayout());

    
    Border border = LAFUtils.getToolTipBorder();
    if (border != null)
      contentPane.setBorder(border);

    contentPane.add(miniViewer, BorderLayout.CENTER);

    miniViewer.textArea.addMouseListener(new MouseAdapter() {
      @Override
      public void mouseClicked(MouseEvent e) {
        if (e.isPopupTrigger()) showPopup(e);
      }

      @Override
      public void mousePressed(MouseEvent e) {
        if (e.isPopupTrigger()) showPopup(e);
      }

      @Override
      public void mouseReleased(MouseEvent e) {
        if (e.isPopupTrigger()) showPopup(e);
      }
    });

    cbmiShowViewRect.setText(TextBundle.getString("MinimapWindow.showViewRect")); //$NON-NLS-1$
    cbmiShowViewRect.setSelected(Config.MINIMAP_VIEWRECT_VISIBLE.getValue());
    miCloseMinimap.setText(TextBundle.getString("MinimapWindow.closeMinimap")); //$NON-NLS-1$

    popupMenu.add(cbmiShowViewRect);
    popupMenu.addSeparator();
    popupMenu.add(miCloseMinimap);

    cbmiShowViewRect.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        miniViewer.textArea.setViewRectVisible(cbmiShowViewRect.isSelected());
        if (cbmiShowViewRect.isSelected())
          miniViewer.textArea.adjustViewRect();
        miniViewer.textArea.repaint();
      }
    });

    miCloseMinimap.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        editPane.tbShowMinimap.setSelected(false);
      }
    });
    
    //GUIUtilities.setWindowTransparency(this, 0.95f);
    
    contentPane.registerKeyboardAction(new ActionListener() {
      public void actionPerformed(final ActionEvent e) {
        editPane.tbShowMinimap.setSelected(false);
      }
    }, "Escape", KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); //$NON-NLS-1$

  }

  /**
   * Show popup.
   *
   * @param e the MouseEvent
   */
  private void showPopup(MouseEvent e) {
    popupMenu.show(e.getComponent(), e.getX(), e.getY());
  }
  
  /**
   * Repaint gutter.
   */
  void repaintGutter() {
    miniViewer.repaintGutter();
  }

  /**
   * Sets the active line visible.
   *
   * @param b the new active line visible
   */
  public void setActiveLineVisible(boolean b) {
    miniViewer.setActiveLineVisible(b);
  }

  /**
   * Sets the line numbers visible.
   *
   * @param b the new line numbers visible
   */
  public void setLineNumbersVisible(boolean b) {
    miniViewer.setLineNumbersVisible(b);
  }

  /**
   * Configure.
   * 
   * @param cfg the cfg
   * @see jnpad.config.Configurable#configure(int)
   */
  @Override
  public void configure(final int cfg) {
    miniViewer.configure(cfg);
  }
}

//////////////////////////////////////////////////////////////////////////////
/**
 * The Class MiniViewer.
 */
class MiniViewer extends JPanel implements Configurable {
  JScrollPane               scrollPane       = new MiniViewerScrollPane();
  MiniViewerTextArea        textArea;

  private ActiveLine        activeLine;
  private SimpleGutter      gutter;
  private JViewport         originalRowHeader;

  EditPane                  editPane;

  /** Logger */
  private static Logger     LOGGER           = Logger.getLogger(MiniViewer.class.getName());

  /** UID */
  private static final long serialVersionUID = -189932606814033055L;
  
  /**
   * Instantiates a new mini view.
   *
   * @param editPane the edit pane
   */
  MiniViewer(EditPane editPane) {
    super(new BorderLayout());
    this.editPane = editPane;
    try {
      jbInit();
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
    }
  }

  /**
   * Inicialización de componentes.
   * 
   * @throws Exception the exception
   */
  private void jbInit() throws Exception {
    textArea = new MiniViewerTextArea(editPane);
    textArea.addMouseListener(new MouseAdapter() {
      @Override
      public void mouseClicked(final MouseEvent e) {
        if (e.getClickCount() == 2 && !e.isPopupTrigger()) {
          final int pos = textArea.getCaretPosition();
          if (e.isControlDown()) {
            editPane.textArea.jumpToOffset(pos);
            editPane.tbShowMinimap.setSelected(false);
          }
          else { 
            editPane.textArea.setCaretPosition(pos);
            TextUtilities.scrollToMiddle(editPane.textArea, pos, 3 / 4f);
          }
        }
      }
    });

    setOpaque(false);

    originalRowHeader = scrollPane.getRowHeader();
    
    setLineNumbersVisible(editPane.isLineNumbersVisible());
    setActiveLineVisible(editPane.isActiveLineVisible());

    scrollPane.getViewport().add(textArea, null);
    add(scrollPane, BorderLayout.CENTER);
  }

  /**
   * Repaint gutter.
   */
  void repaintGutter() {
    if (gutter != null) 
      gutter.repaint();
  }
  
  /**
   * Center viewport.
   */
  void centerViewport() {
    textArea.scrollToMiddle(textArea.getCaretPosition());
  }

  /**
   * Sets the active line visible.
   *
   * @param b the new active line visible
   */
  void setActiveLineVisible(boolean b) {
    if (b) {
      if (activeLine == null)
        activeLine = new ActiveLine(textArea);
      else
        activeLine.setVisible(true);
    }
    else if (activeLine != null)
      activeLine.setVisible(false);
    revalidate();
    repaint();
  }
  
  /**
   * Sets the line numbers visible.
   *
   * @param b the new line numbers visible
   */
  void setLineNumbersVisible(boolean b) {
    if (b) {
      if (gutter == null)
        gutter = new SimpleGutter(textArea);
      scrollPane.setRowHeaderView(gutter);
    }
    else
      scrollPane.setRowHeader(originalRowHeader);
    revalidate();
    repaint();
  }

  /**
   * Configure.
   *
   * @param cfg the cfg
   * @see jnpad.config.Configurable#configure(int)
   */
  @Override
  public void configure(final int cfg) {
    textArea.configure(cfg);
    if (gutter       != null) gutter.configure(cfg);
    if (activeLine   != null) activeLine.configure(cfg);
    if ( (cfg & CFG_VIEW) != 0) {
      setLineNumbersVisible(editPane.isLineNumbersVisible());
      setActiveLineVisible(editPane.isActiveLineVisible());
    }
  }
  
}
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
/**
 * The Class MiniViewerScrollPane.
 */
class MiniViewerScrollPane extends MiniScrollPane {
  /** UID */
  private static final long serialVersionUID = -6453236981939458697L;

  /**
   * Instantiates a new mini viewer scroll pane.
   */
  MiniViewerScrollPane() {
    setBorder(null);
    if (Config.isDistractionFreeMode()) {
      setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER);
      setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_NEVER);
    }
    else {
      setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
      setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
    }
  }
}
//////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////
/**
 * The Class MiniViewerTextArea.
 */
class MiniViewerTextArea extends AbstractTextArea {
  private JNPadTextArea            textArea;

  private JViewport                viewPort;
  private Rectangle                viewRect;
  private JScrollPane              scrollPane;
  private boolean                  isViewRectVisible;

  private final AdjustmentListener adjustmentListener = new AdjustmentListener() {
                                                        public void adjustmentValueChanged(AdjustmentEvent e) {
                                                          adjustViewRect();
                                                          repaint();
                                                        }
                                                      };

  /** UID */
  private static final long        serialVersionUID   = 3948063257147844952L;

  /**
   * Instantiates a new mini viewer text area.
   *
   * @param editPane the edit pane
   */
  MiniViewerTextArea(EditPane editPane) {
    super(editPane);

    textArea = editPane.textArea;
    
    setDocument(editPane.getDocument());

    scrollPane = editPane.scrollPane;
    viewPort   = scrollPane.getViewport();
    
    setViewRectVisible(Config.MINIMAP_VIEWRECT_VISIBLE.getValue());

    setUI(new MiniViewerTextAreaUI());

    setEditable(false);
    
    doConfigure(CFG_ALL);

    setCaretPosition(textArea.getCaretPosition());
  }
  
  /**
   * Adjust view rect.
   */
  void adjustViewRect() {
    if (isViewRectVisible() && !textArea.getLineWrap() && textArea.getText().length() > 0)
      adjustViewRect(viewPort);
  }

  /**
   * Adjust view rect.
   *
   * @param vp the JViewport
   */
  private void adjustViewRect(JViewport vp) {
    Rectangle vr = vp.getViewRect();
    Point p = vr.getLocation();
    
    int startIndex = textArea.viewToModel(p);
    p.x += vr.width;
    p.y += vr.height;
    int endIndex = textArea.viewToModel(p);

    if(endIndex - startIndex >= 0) {
      final String s = "WWWWWWWWW"; //$NON-NLS-1$
      int sw1 = textArea.getFontMetrics(textArea.getFont()).stringWidth(s);
      int sw2 = getFontMetrics(getFont()).stringWidth(s);
      
      try {
        Rectangle r1 = modelToView(startIndex);
        Rectangle r2 = modelToView(endIndex);
        
        if(r1 != null && r2 != null) {
          final int x = vr.x * sw2 / sw1;
          final int y = r1.y;
          final int w = vr.width * sw2 / sw1;
          final int h = r2.y - r1.y;
          
          viewRect = new Rectangle(x, y, w, h);
        }
        else 
          viewRect = null;
      }
      catch (BadLocationException e1) {
        e1.printStackTrace();
      }
    }
  }

  /**
   * Gets the view rect.
   *
   * @return the view rect
   * @see jnpad.text.AbstractTextArea#getViewRect()
   */
  @Override
  public Rectangle getViewRect() {return viewRect;}

  /**
   * Checks if is view rect visible.
   *
   * @return true, if is view rect visible
   * @see jnpad.text.AbstractTextArea#isViewRectVisible()
   */
  @Override
  public boolean isViewRectVisible() {
    return isViewRectVisible;
  }
  
  /**
   * Sets the view rect visible.
   *
   * @param b is view rect visible
   * @see jnpad.text.AbstractTextArea#setViewRectVisible(boolean)
   */
  @Override
  public void setViewRectVisible(boolean b) {
    if (isViewRectVisible != b) {
      System.out.println(b);
      if (b) {
        scrollPane.getHorizontalScrollBar().addAdjustmentListener(adjustmentListener);
        scrollPane.getVerticalScrollBar().addAdjustmentListener(adjustmentListener);
      }
      else {
        scrollPane.getHorizontalScrollBar().removeAdjustmentListener(adjustmentListener);
        scrollPane.getVerticalScrollBar().removeAdjustmentListener(adjustmentListener);
      }
      isViewRectVisible = b;
      Config.MINIMAP_VIEWRECT_VISIBLE.setValue(b);
    }
  }
  
  /**
   * Configure.
   *
   * @param cfg the cfg
   * @see jnpad.config.Configurable#configure(int)
   */
  @Override
  public void configure(final int cfg) {
    /*Document doc = getDocument();
    if (doc != null && doc instanceof JNPadDocument) {
      Scheme scheme = ((JNPadDocument) doc).getMiniScheme();
      if (scheme != null)
        scheme.configure(cfg);
    }*/
    doConfigure(cfg);
  }
  
  /**
   * Do configure.
   *
   * @param cfg the cfg
   */
  private void doConfigure(final int cfg) {
    if ( (cfg & CFG_COLOR) != 0) {
      Color bg = textArea.getBackground();
      if (LAFUtils.isNimbusLAF()) {
        UIDefaults overrides = new UIDefaults();
        overrides.put("TextPane[Enabled].backgroundPainter", bg); //$NON-NLS-1$
        putClientProperty("Nimbus.Overrides", overrides); //$NON-NLS-1$
        putClientProperty("Nimbus.Overrides.InheritDefaults", Boolean.TRUE); //$NON-NLS-1$
      }
      setBackground(bg);
      setForeground(textArea.getForeground());
      setSelectionColor(textArea.getSelectionColor());
      setSelectedTextColor(textArea.getSelectedTextColor());
      setCaretColor(textArea.getCaretColor());
    }
    if ( (cfg & CFG_FONT) != 0) {
      setFont(textArea.getFont().deriveFont(Config.MINIMAP_FONT_SIZE.getValue()));
    }
    if ( (cfg & CFG_VIEW) != 0) {
      setRightMarginLineVisible(textArea.isRightMarginLineVisible());
      setRightMarginLineWidth(textArea.getRightMarginLineWidth());
      setLineWrap(textArea.getLineWrap());
      setWrapStyleWord(textArea.getWrapStyleWord());
      setTabSize(textArea.getTabSize());
    }
  }
  
  /**
   * Gets the content type.
   *
   * @return the content type
   * @see jnpad.text.AbstractTextArea#getContentType()
   */
  @Override
  public String getContentType() {
    return textArea.getContentType();
  }
  
  /**
   * Checks if is right margin line visible.
   *
   * @return true, if is right margin line visible
   * @see jnpad.text.AbstractTextArea#isRightMarginLineVisible()
   */
  @Override
  public boolean isRightMarginLineVisible() {
    return textArea.isRightMarginLineVisible();
  }

  /**
   * Gets the right margin line width.
   *
   * @return the right margin line width
   * @see jnpad.text.AbstractTextArea#getRightMarginLineWidth()
   */
  @Override
  public int getRightMarginLineWidth() {
    return textArea.getRightMarginLineWidth();
  }

  /**
   * Gets the right margin line color.
   *
   * @return the right margin line color
   * @see jnpad.text.AbstractTextArea#getRightMarginLineColor()
   */
  @Override
  public Color getRightMarginLineColor() {
    return textArea.getRightMarginLineColor();
  }

  /**
   * Checks if is main.
   *
   * @return true, if is main
   * @see jnpad.text.AbstractTextArea#isMain()
   */
  @Override
  boolean isMain() {
    return textArea.isMain();
  }
}
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
/**
 * The Class MiniViewerTextAreaUI.
 */
class MiniViewerTextAreaUI extends BasicTextAreaUI implements ContentTypes {
  private static final EditorKit defaultKit = new JNPadTextAreaEditorKit();

  /**
   * Creates the ui.
   *
   * @param ta the text area
   * @return the component ui
   */
  public static ComponentUI createUI(JComponent ta) {
    return new MiniViewerTextAreaUI();
  }

  /**
   * Property change.
   * 
   * @param evt the evt
   * @see javax.swing.plaf.basic.BasicTextAreaUI#propertyChange(java.beans.PropertyChangeEvent)
   */
  @Override
  protected void propertyChange(PropertyChangeEvent evt) {
    super.propertyChange(evt);
    if (evt.getPropertyName().equals("jnpad.contentType")) { //$NON-NLS-1$
      // rebuild the view
      modelChanged();
    }
  }

  /**
   * Creates the view.
   * 
   * @param elem the elem
   * @return the view
   * @see javax.swing.plaf.basic.BasicTextAreaUI#create(javax.swing.text.Element)
   */
  @Override
  public View create(Element elem) {
    JTextComponent c = getComponent();
    if (c instanceof MiniViewerTextArea) {
      MiniViewerTextArea area = (MiniViewerTextArea) c;
      String style = area.getContentType();
      if (JAVA.equals(style))       return new JavaView(elem, true);
      if (C.equals(style))          return new CView(elem, true);
      if (CPP.equals(style))        return new CPPView(elem, true);
      if (PROPERTIES.equals(style)) return new PropertiesView(elem, true);
      if (ASN1.equals(style))       return new ASN1View(elem, true);
      if (PLAIN.equals(style)) {
        if (area.getLineWrap())
          return new WrappedPlainView2(elem, area.getWrapStyleWord());
        return new PlainView2(elem, true);
      }
    }
    return super.create(elem);
  }

  /**
   * Install ui.
   * 
   * @param c the c
   * @see javax.swing.plaf.basic.BasicTextUI#installUI(javax.swing.JComponent)
   */
  @Override
  public void installUI(JComponent c) {
    if (!(c instanceof MiniViewerTextArea)) {
      throw new Error("MiniViewerTextAreaUI needs an instance of MiniViewerTextArea!"); //$NON-NLS-1$
    }
    super.installUI(c);
  }

  /**
   * Gets the editor kit.
   * 
   * @param tc the tc
   * @return the editor kit
   * @see javax.swing.plaf.basic.BasicTextUI#getEditorKit(javax.swing.text.JTextComponent)
   */
  @Override
  public EditorKit getEditorKit(JTextComponent tc) {
    return defaultKit;
  }
}
//////////////////////////////////////////////////////////////////////////////
