/*
 * 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.print;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.font.FontRenderContext;
import java.awt.print.Book;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.util.Calendar;
import java.util.Vector;

import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.Segment;

import jnpad.JNPadBundle;
import jnpad.ui.ReporterUtilities;
import jnpad.util.Utilities;

/**
 * The Class PrintText.
 *
 * @version 0.3
 * @since   jNPad v0.1
 */
@SuppressWarnings("unused")
public class PrintText {
  private int             _wrapOffset = 0;         // Used to determine where to begin a wrapped line.
  private String          _docTitle;               // Used for document title (i.e. file name) when including the page header
  private String[]        _text;                   // Text to print.
  private PrintingOptions _printOptions;           // Print options (i.e. font, print header, etc.)
  private int             _tabSize    = 4;         // Tab stop if hard tabs are used.

  /**
   * Instantiates a new prints the text.
   *
   * @param doc the doc
   */
  public PrintText(Document doc) {
    this(doc, Utilities.EMPTY_STRING, new PrintingOptions(), 4);
  }

  /**
   * Instantiates a new prints the text.
   *
   * @param doc Document
   * @param docTitle String
   * @param printOptions PrintingOptions
   * @param tabSize int
   */
  public PrintText(Document doc, String docTitle, PrintingOptions printOptions, int tabSize) {
    _printOptions = printOptions;
    _tabSize = tabSize;

    if (docTitle != null) {
      _docTitle = docTitle;
    }
    else {
      // If a new doc and no title, set docTitle to "New Document"
      _docTitle = JNPadBundle.getString("PrintText.new"); //$NON-NLS-1$
    }

    // Get Root element of the document
    Element root = doc.getDefaultRootElement();

    //get the number of lines (i.e. child elements)
    int count = root.getElementCount();

    //Allocate the array
    String lines[] = new String[count];

    Segment segment = new Segment();

    // Get each line element, get its text and put it in the string array
    for (int i = 0; i < count; i++) {
      Element lineElement = root.getElement(i);
      try {
        doc.getText(lineElement.getStartOffset(), 
                    lineElement.getEndOffset() - lineElement.getStartOffset(),
                    segment);
        lines[i] = segment.toString();
      }
      catch (BadLocationException ex) {
       // Nothing gets added to the Array if there is a bad location
      }
    }
    _text = lines;
    printTextArray();
  }

  /**
   * Prints the text array.
   */
  void printTextArray() {
    try {
      PrinterJob pj = PrinterJob.getPrinterJob(); // create a printjob

      _text = removeEOLChar();

      if (_printOptions.getPrintLineNumbers()) {
        _text = addLineNumbers();
      }
      if (_printOptions.getWrapText()) {
        _text = wrapText();
      }

        Book _pages = pageinateText();

      try {
        pj.setPageable(_pages); // set the book pageable so the printjob knows we are printing more than one page (maybe)
        if (pj.printDialog()) {
          pj.print(); // print.  This calls each Page object's print method
        }
      }
      // catch any errors and be as ambiguous about them as possible :)
      catch (Exception ex) {
        reportError();
      }
    }
    catch (Exception ex) {
      reportError();
    }
  }

  /**
   * Report error.
   */
  private static void reportError() {
    ReporterUtilities.reportError(null, 
                                  JNPadBundle.getString("PrintText.error.message"),  //$NON-NLS-1$
                                  JNPadBundle.getString("PrintText.error.title")); //$NON-NLS-1$
  }

  /**
   * Removes the eol char.
   *
   * @return String[]
   */
  private String[] removeEOLChar() {
    String temp1, temp2, temp3;
    int lineCount = _text.length;
    String[] newText = new String[lineCount];
    int offset;

    for (int i = 0; i < lineCount; i++) {
      if (_text[i].length() == 1) {
        newText[i] = Utilities.SPACE_STRING;
      }
      else {
        temp1 = _text[i].substring(_text[i].length() - 2, _text[i].length() - 1);
        temp2 = _text[i].substring(_text[i].length() - 1, _text[i].length());
        if (temp1.compareTo(Utilities.CR_STRING) == 0 || temp1.compareTo(Utilities.LF_STRING) == 0) {
          offset = 2;
        }
        else if (temp2.compareTo(Utilities.CR_STRING) == 0 || temp2.compareTo(Utilities.LF_STRING) == 0) {
          offset = 1;
        }
        else {
          offset = 0;
        }
        temp3 = _text[i].substring(0, _text[i].length() - offset);

        // Process tabs.  Assume tab stops.
        StringBuilder temp4 = new StringBuilder();
        int length = temp3.length();
        for (int j = 0; j < length; j++) {
          if (Utilities.TAB_STRING.equals(temp3.substring(j, j + 1))) {
            // Calcualte the numbe of spaces to the tab stop.
            int numSpaces = (temp4.length()) % _tabSize;

            if (numSpaces == 0) {
              numSpaces = _tabSize;
            }
            for (int x = 0; x < numSpaces; x++) {
              temp4.append(Utilities.SPACE_STRING);
            }
          }
          else {
            temp4.append(temp3.substring(j, j + 1));
          }
        }
        newText[i] = temp4.toString();
      }
    }
    return newText;
  }

  /**
   * Adds the line numbers.
   *
   * @return the string[]
   */
  private String[] addLineNumbers() {
    int numLines = _text.length;
    int totalNumSpaces;
    String temp;
    String[] newText = new String[numLines];

    // Get the total number of digits in last line number
    // So that spacing and alignment can be done properly.
    Integer lines = numLines;
    temp = lines.toString();
    totalNumSpaces = temp.length();

    // Set the wrap offset so that we can start wrapped lines in the proper place.
    _wrapOffset = totalNumSpaces + 3;

    for (int i = 0; i < numLines; i++) {
      StringBuilder num = new StringBuilder();
      num.append(i + 1);
      int numLen = num.length();

      StringBuilder lineNum = new StringBuilder();
      for (int j = 0; j < (totalNumSpaces - numLen); j++) {
        lineNum.append(Utilities.SPACE);
      }
      lineNum.append(num.toString());

      newText[i] = lineNum.toString() + ".  " + _text[i]; //$NON-NLS-1$
    }

    return newText;

  }

  /**
   * Wrap text.
   *
   * @return the string[]
   */
  private String[] wrapText() {
    String currentLine;
    String tempString = null;
    Vector<String> temp = new Vector<String>();
    int lineCount = _text.length;
    int newLineCount;
    StringBuilder wrapSpaces = new StringBuilder();
    int i;
    PageFormat pgfmt = _printOptions.getPageFormat();
    Font pageFont = _printOptions.getPageFont();
    double pageWidth = pgfmt.getImageableWidth();

    for (i = 0; i < _wrapOffset; i++) {
      wrapSpaces.append(Utilities.SPACE);
    }

    for (i = 0; i < lineCount; i++) {
      currentLine = _text[i];
      while (pageFont.getStringBounds(currentLine,
                                      new FontRenderContext(pageFont.getTransform(), false, false)).getWidth() > pageWidth) {
        int numChars = (int) (currentLine.length() * pageWidth / pageFont.getStringBounds(currentLine,
            new FontRenderContext(pageFont.getTransform(), false, false)).getWidth());
        temp.add(currentLine.substring(0, numChars));
        currentLine = wrapSpaces.toString() + currentLine.substring(numChars, currentLine.length());
      }
      temp.add(currentLine);
    }

    newLineCount = temp.size();
    String[] newText = new String[newLineCount];

    for (int j = 0; j < newLineCount; j++) {
      newText[j] = temp.get(j);
    }

    return newText;
  }

  /**
   * Pageinate text.
   *
   * @return the book
   */
  private Book pageinateText() {
    Book book = new Book();
    int linesPerPage; // lines on one page
    int currentLine = 0; // line I am  currently reading
    int pageNum = 0; // page #
    PageFormat pgfmt = _printOptions.getPageFormat();
    Font pageFont = _printOptions.getPageFont();
    int height = (int) pgfmt.getImageableHeight(); // height of a page
    int pages; // number of pages

    linesPerPage = height / (pageFont.getSize() + 2); // number of lines on a page
    pages = ( _text.length / linesPerPage); // set number of pages
    String[] pageText; // one page of text
    String readString; // a temporary string to read from master string

    convertUnprintables(); // method to keep out errors

    if (_printOptions.getPrintHeader()) {
      linesPerPage = linesPerPage - 2;
    }

    while (pageNum <= pages) {
      pageText = new String[linesPerPage]; // create a new page
      for (int x = 0; x < linesPerPage; x++) {
        try {
          readString = _text[currentLine]; // read the string
        }
        catch (ArrayIndexOutOfBoundsException e) {
          readString = Utilities.SPACE_STRING;
        }
        pageText[x] = readString; // add to the page

        currentLine++;
      }
      pageNum++; // increase the page number I am on
      book.append(new Page(pageText, pageNum), pgfmt); // create a new page object with the text and add it to the book

    }
    return book; // return the completed book

  }

  /**
   * Converts unprintable things to a space.  stops some errors.
   */
  private void convertUnprintables() {
    String tempString;
    int i = _text.length;
    while (i > 0) {
      i--;
      tempString = _text[i];
      if (Utilities.isEmptyString(tempString)) {
        _text[i] = Utilities.SPACE_STRING;
      }
    }
  }
  
  /////////////////////////////////////////////////////////////////////////
  /**
   * An inner class that defines one page of text based on data about the
   * PageFormat etc. from the book defined in the parent class
   */
  class Page implements Printable {
    private String[] pageText_;      // the text for the page
    private int      pageNumber_ = 0;

    /**
     * Instantiates a new page.
     *
     * @param text the text
     * @param pageNum the page num
     */
    Page(String[] text, int pageNum) {
      this.pageText_ = text; // set the page's text
      this.pageNumber_ = pageNum; // set page number.
    }

    /**
     * Prints the.
     *
     * @param graphics the graphics
     * @param pageFormat the page format
     * @param pageIndex the page index
     * @return the int
     * @throws PrinterException the printer exception
     * @see java.awt.print.Printable#print(java.awt.Graphics, java.awt.print.PageFormat, int)
     */
    @Override
    public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
      int pos;
      int posOffset = 1;
      double pageWidth = pageFormat.getImageableWidth();
      Font pageFont = _printOptions.getPageFont();

      if (_printOptions.getPrintHeader()) {
        StringBuilder header = new StringBuilder();
        StringBuilder pageNumText = new StringBuilder();
        int i = 0;
        int headerPos = 0;
        int numSpaces = 0;

        Calendar date = Calendar.getInstance();
        header.append(date.get(Calendar.DAY_OF_MONTH));
        header.append('/');
        header.append(date.get(Calendar.MONTH) + 1);
        header.append('/');
        header.append(date.get(Calendar.YEAR));

        pageNumText.append(JNPadBundle.getString("PrintText.page")); //$NON-NLS-1$
        pageNumText.append(pageNumber_);

        int xPos;
        double margin = (pageFormat.getWidth() - pageFormat.getImageableWidth()) / 2;
        graphics.setFont(_printOptions.getHeaderFont());
        graphics.setColor(Color.black);
        pos = (int) pageFormat.getImageableY() + (_printOptions.getHeaderFont().getSize() + 2);
        graphics.drawString(header.toString(), (int) pageFormat.getImageableX(), pos); // draw a line of text
        xPos = (int) ( (pageFormat.getWidth() / 2.0) - (graphics.getFontMetrics().stringWidth(_docTitle) / 2.0));
        graphics.drawString(_docTitle, xPos, pos);
        xPos = (int) (pageFormat.getWidth() - margin - graphics.getFontMetrics().stringWidth(pageNumText.toString()));
        graphics.drawString(pageNumText.toString(), xPos, pos);
        posOffset = 3;
      }

      graphics.setFont(pageFont); // Set the font
      graphics.setColor(Color.black); // set color

      for (int x = 0; x < (pageText_.length); x++) {
        pos = (int) pageFormat.getImageableY() + (pageFont.getSize() + 2) * (x + posOffset);
        graphics.drawString(this.pageText_[x], (int) pageFormat.getImageableX(), pos); // draw a line of text
      }

      return Printable.PAGE_EXISTS; // print the page
    }
  }
  /////////////////////////////////////////////////////////////////////////

}
