/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2016 The Kuali Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.kfs.module.tem.pdf;

import com.lowagie.text.BadElementException;
import com.lowagie.text.Cell;
import com.lowagie.text.Chunk;
import com.lowagie.text.Document;
import com.lowagie.text.Font;
import com.lowagie.text.FontFactory;
import com.lowagie.text.Image;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.Table;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfWriter;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.module.tem.document.TravelReimbursementDocument;
import org.kuali.kfs.module.tem.report.util.BarcodeHelper;
import org.kuali.kfs.sys.context.SpringContext;

import java.io.OutputStream;
import java.util.Collection;
import java.util.Map;

import static com.lowagie.text.Element.ALIGN_RIGHT;
import static com.lowagie.text.PageSize.LETTER;
import static com.lowagie.text.Rectangle.NO_BORDER;
import static java.awt.Color.BLACK;
import static org.kuali.kfs.module.tem.TemConstants.TravelReimbursementParameters.FAX_NUMBER;

/**
 * Class representing a PDF Coversheet of the {@link TravelReimbursementDocument}.
 */
public class Coversheet implements PdfStream {
    private static final int ALIGNMENT_MARK_HEIGHT = 8;
    private static final int ALIGNMENT_MARK_WIDTH = 10;
    private static final int ALIGNMENT_MARGIN = 5;
    private static final int TOP_MARGIN = 50;

    private String initiatorName;
    private String initiatorPrincipalName;
    private String initiatorPhone;
    private String initiatorEmail;
    private String travelerName;
    private String travelerPrincipalName;
    private String travelerPhone;
    private String travelerEmail;
    private String instructions;
    private String mailTo;
    private String date;
    private String tripId;
    private String destination;
    private String documentNumber;


    private Collection<Map<String, String>> expenses;

    /**
     * Sets the initiatorName attribute
     *
     * @param initiatorName is the String initiatorName value
     */
    public void setInitiatorName(final String initiatorName) {
        this.initiatorName = initiatorName;
    }

    /**
     * Gets the initiatorName attribute
     *
     * @return String value of initiatorName
     */
    public String getInitiatorName() {
        if (initiatorName == null) {
            return "";
        }
        return this.initiatorName;
    }

    /**
     * Sets the initiatorPrincipalName attribute
     *
     * @param initiatorPrincipalName is the String initiatorPrincipalName value
     */
    public void setInitiatorPrincipalName(final String initiatorPrincipalName) {
        this.initiatorPrincipalName = initiatorPrincipalName;
    }

    /**
     * Gets the initiatorPrincipalName attribute
     *
     * @return String value of initiatorPrincipalName
     */
    public String getInitiatorPrincipalName() {
        if (initiatorPrincipalName == null) {
            return "";
        }
        return this.initiatorPrincipalName;
    }

    /**
     * Sets the initiatorPhone attribute
     *
     * @param initiatorPhone is the String initiatorPhone value
     */
    public void setInitiatorPhone(final String initiatorPhone) {
        this.initiatorPhone = initiatorPhone;
    }

    /**
     * Gets the initiatorPhone attribute
     *
     * @return String value of initiatorPhone
     */
    public String getInitiatorPhone() {
        if (initiatorPhone == null) {
            return "";
        }
        return this.initiatorPhone;
    }

    /**
     * Sets the initiatorEmail attribute
     *
     * @param initiatorEmail is the String initiatorEmail value
     */
    public void setInitiatorEmail(final String initiatorEmail) {
        this.initiatorEmail = initiatorEmail;
    }

    /**
     * Gets the initiatorEmail attribute
     *
     * @return String value of initiatorEmail
     */
    public String getInitiatorEmail() {
        if (initiatorEmail == null) {
            return "";
        }
        return this.initiatorEmail;
    }

    /**
     * Sets the travelerName attribute
     *
     * @param travelerName is the String travelerName value
     */
    public void setTravelerName(final String travelerName) {
        this.travelerName = travelerName;
    }

    /**
     * Gets the travelerName attribute
     *
     * @return String value of travelerName
     */
    public String getTravelerName() {
        if (travelerName == null) {
            return "";
        }

        return this.travelerName;
    }

    /**
     * Sets the travelerPrincipalName attribute
     *
     * @param travelerPrincipalName is the String travelerPrincipalName value
     */
    public void setTravelerPrincipalName(final String travelerPrincipalName) {
        this.travelerPrincipalName = travelerPrincipalName;
    }

    /**
     * Gets the travelerPrincipalName attribute
     *
     * @return String value of travelerPrincipalName
     */
    public String getTravelerPrincipalName() {
        if (travelerPrincipalName == null) {
            return "";
        }
        return this.travelerPrincipalName;
    }

    /**
     * Sets the travelerPhone attribute
     *
     * @param travelerPhone is the String travelerPhone value
     */
    public void setTravelerPhone(final String travelerPhone) {
        this.travelerPhone = travelerPhone;
    }

    /**
     * Gets the travelerPhone attribute
     *
     * @return String value of travelerPhone
     */
    public String getTravelerPhone() {
        if (travelerPhone == null) {
            return "";
        }
        return this.travelerPhone;
    }

    /**
     * Sets the travelerEmail attribute
     *
     * @param travelerEmail is the String travelerEmail value
     */
    public void setTravelerEmail(final String travelerEmail) {
        this.travelerEmail = travelerEmail;
    }

    /**
     * Gets the travelerEmail attribute
     *
     * @return String value of travelerEmail
     */
    public String getTravelerEmail() {
        if (travelerEmail == null) {
            return "";
        }
        return this.travelerEmail;
    }

    /**
     * Sets the instructions attribute
     *
     * @param instructions is the String instructions value
     */
    public void setInstructions(final String instructions) {
        this.instructions = instructions;
    }

    /**
     * Gets the instructions attribute
     *
     * @return String value of instructions
     */
    public String getInstructions() {
        if (instructions == null) {
            return "";
        }
        return this.instructions;
    }

    /**
     * Sets the date attribute
     *
     * @param date is the String date value
     */
    public void setDate(final String date) {
        this.date = date;
    }

    /**
     * Gets the date attribute
     *
     * @return String value of date
     */
    public String getDate() {
        if (date == null) {
            return "";
        }
        return this.date;
    }

    /**
     * Sets the mailTo attribute
     *
     * @param mailTo is the String mailTo value
     */
    public void setMailTo(final String mailTo) {
        this.mailTo = mailTo;
    }

    /**
     * Gets the mailTo attribute
     *
     * @return String value of mailTo
     */
    public String getMailTo() {
        if (mailTo == null) {
            return "";
        }
        return this.mailTo;
    }

    /**
     * Sets the destination attribute
     *
     * @param destination is the String destination value
     */
    public void setDestination(final String destination) {
        this.destination = destination;
    }

    /**
     * Gets the destination attribute
     *
     * @return String value of destination
     */
    public String getDestination() {
        if (destination == null) {
            return "";
        }

        return this.destination;
    }

    /**
     * Sets the tripId attribute
     *
     * @param tripId is the String tripId value
     */
    public void setTripId(final String tripId) {
        this.tripId = tripId;
    }

    /**
     * Gets the tripId attribute
     *
     * @return String value of tripId
     */
    public String getTripId() {
        if (tripId == null) {
            return "";
        }
        return this.tripId;
    }

    /**
     * Sets the documentNumber attribute
     *
     * @param documentNumber is the String documentNumber value
     */
    public void setDocumentNumber(final String documentNumber) {
        this.documentNumber = documentNumber;
    }

    /**
     * Gets the documentNumber attribute
     *
     * @return String value of documentNumber
     */
    public String getDocumentNumber() {
        return this.documentNumber;
    }

    /**
     * Creates instructions section of the coverpage
     *
     * @returns a {@link Paragraph} for the PDF
     */
    protected Paragraph getInstructionsParagraph() {
        final Font headerFont = FontFactory.getFont(FontFactory.HELVETICA, 12, Font.BOLD);
        final Font normalFont = FontFactory.getFont(FontFactory.HELVETICA, 12, Font.NORMAL);
        final Paragraph retval = new Paragraph();
        retval.add(new Chunk("Instructions", headerFont));
        retval.add(Chunk.NEWLINE);
        retval.add(new Phrase(getInstructions(), normalFont));
        return retval;
    }

    /**
     * Creates mailTo section for the coversheet. The MailTo section is where the coversheet will be mailed.
     *
     * @returns a {@link Paragraph} for the PDF
     */
    protected Paragraph getMailtoParagraph() {
        final Font headerFont = FontFactory.getFont(FontFactory.HELVETICA, 12, Font.BOLD);
        final Font normalFont = FontFactory.getFont(FontFactory.HELVETICA, 12, Font.NORMAL);
        final Paragraph retval = new Paragraph();
        retval.add(new Chunk("Mail coversheet to:", headerFont));
        retval.add(Chunk.NEWLINE);
        retval.add(new Phrase(getMailTo(), normalFont));
        return retval;
    }

    /**
     * Information about the person that initiated the {@link TravelReimbursementDocument}
     */
    protected Cell getInitiatorInfo() throws BadElementException {
        final StringBuilder strBuilder = new StringBuilder();
        strBuilder.append(getInitiatorName()).append("\n")
            .append(getInitiatorPrincipalName()).append("\n")
            .append(getInitiatorPhone()).append("\n")
            .append(getInitiatorEmail()).append("\n");
        final Cell retval = getBorderlessCell(strBuilder.toString());
        return retval;
    }

    /**
     * Information about the traveler described in the trip for the {@link TravelReimbursementDocument}
     */
    protected Cell getTravelerInfo() throws BadElementException {
        final StringBuilder strBuilder = new StringBuilder();
        strBuilder.append(getTravelerName()).append("\n")
            .append(getTravelerPrincipalName()).append("\n")
            .append(getTravelerPhone()).append("\n")
            .append(getTravelerEmail()).append("\n");
        final Cell retval = getBorderlessCell(strBuilder.toString());
        return retval;
    }

    /**
     * Get the PDF Table containing trip information like trip id, date, and destination
     *
     * @returns {@link Table} used for a PDF
     */
    protected Table getTripInfo() throws BadElementException {
        final Table retval = new Table(3);
        retval.setWidth(100f);
        retval.setBorder(NO_BORDER);
        retval.addCell(getHeaderCell("Trip/Event ID"));

        final Cell dateHeaderCell = getHeaderCell("Date");

        retval.addCell(dateHeaderCell);
        retval.addCell(getHeaderCell("Destination/Event Name"));
        retval.endHeaders();
        retval.addCell(getBorderlessCell(getTripId()));

        final Cell dateCell = getBorderlessCell(getDate());

        retval.addCell(dateCell);
        retval.addCell(getBorderlessCell(getDestination()));
        return retval;
    }

    /**
     * Get the PDF Table with personal information about the initiator and traveler
     *
     * @returns {@link Table} used for a PDF
     */
    protected Table getPersonalInfo() throws BadElementException {
        final Table retval = new Table(2);
        retval.setWidth(100f);
        retval.setBorder(NO_BORDER);
        retval.addCell(getHeaderCell("Traveler"));

        final Cell initiatorHeaderCell = getHeaderCell("Request Submitted By");

        retval.addCell(initiatorHeaderCell);
        retval.endHeaders();
        retval.addCell(getTravelerInfo());

        final Cell initiatorCell = getInitiatorInfo();

        retval.addCell(initiatorCell);
        return retval;
    }

    public void setExpenses(final Collection<Map<String, String>> expenses) {
        this.expenses = expenses;
    }

    public Table getExpenses() throws BadElementException {
        final Table retval = new Table(3);
        retval.setWidth(100f);
        retval.setBorder(NO_BORDER);
        retval.addCell(getHeaderCell("Expenses"));
        retval.addCell(getHeaderCell("Amount"));
        retval.addCell(getHeaderCell("Receipt Required?"));
        retval.endHeaders();

        for (final Map<String, String> expense : expenses) {
            retval.addCell(getBorderlessCell(expense.get("expenseType")));
            retval.addCell(getBorderlessCell(expense.get("amount")));
            retval.addCell(getBorderlessCell(expense.get("receipt")));
        }
        return retval;
    }

    /**
     * Helper method to create a Header Cell from text
     *
     * @returns {@link Cell} with the header flag set
     */
    protected Cell getBorderlessCell(final String text) throws BadElementException {
        final Font normalFont = FontFactory.getFont(FontFactory.HELVETICA, 12, Font.NORMAL);
        final Cell retval = new Cell(new Chunk(text, normalFont));
        retval.setBorder(NO_BORDER);
        return retval;
    }

    /**
     * Helper method to create a Header Cell from text
     *
     * @returns {@link Cell} with the header flag set
     */
    protected Cell getHeaderCell(final String text) throws BadElementException {
        final Font headerFont = FontFactory.getFont(FontFactory.HELVETICA, 12, Font.BOLD);
        final Cell retval = new Cell(new Chunk(text, headerFont));
        retval.setBorder(NO_BORDER);
        retval.setHeader(true);
        return retval;
    }

    protected void upperLeftAlignmentMark(final PdfContentByte cb) {
        cb.saveState();
        cb.rectangle(ALIGNMENT_MARGIN,
            (LETTER.height() + TOP_MARGIN) - ALIGNMENT_MARK_HEIGHT - ALIGNMENT_MARGIN,
            ALIGNMENT_MARK_WIDTH,
            ALIGNMENT_MARK_HEIGHT);
        cb.setColorFill(BLACK);
        cb.fill();
        cb.restoreState();
    }

    protected void lowerLeftAlignmentMark(final PdfContentByte cb) {
        cb.saveState();
        cb.rectangle(ALIGNMENT_MARGIN,
            ALIGNMENT_MARGIN,
            ALIGNMENT_MARK_WIDTH,
            ALIGNMENT_MARK_HEIGHT);
        cb.setColorFill(BLACK);
        cb.fill();
        cb.restoreState();
    }

    protected void lowerRightAlignmentMark(final PdfContentByte cb) {
        cb.saveState();
        cb.rectangle(LETTER.width() - (ALIGNMENT_MARGIN * 4) - ALIGNMENT_MARK_WIDTH,
            ALIGNMENT_MARGIN,
            ALIGNMENT_MARK_WIDTH,
            ALIGNMENT_MARK_HEIGHT);
        cb.setColorFill(BLACK);
        cb.fill();
        cb.restoreState();
    }

    protected void upperRightAlignmentMark(final PdfContentByte cb) {
        cb.saveState();
        cb.rectangle(LETTER.width() - (ALIGNMENT_MARGIN * 4) - ALIGNMENT_MARK_WIDTH,
            (LETTER.height() + TOP_MARGIN) - ALIGNMENT_MARGIN - ALIGNMENT_MARK_HEIGHT,
            ALIGNMENT_MARK_WIDTH,
            ALIGNMENT_MARK_HEIGHT);
        cb.setColorFill(BLACK);
        cb.fill();
        cb.restoreState();
    }

    protected void drawAlignmentMarks(final PdfContentByte cb) {
        upperLeftAlignmentMark(cb);
        lowerLeftAlignmentMark(cb);
        upperRightAlignmentMark(cb);
        lowerRightAlignmentMark(cb);
    }

    /**
     * @throws Exception
     * @see org.kuali.kfs.module.tem.pdf.PdfStream#print(java.io.OutputStream)
     */
    @Override
    public void print(final OutputStream stream) throws Exception {
        final Font titleFont = FontFactory.getFont(FontFactory.HELVETICA, 20, Font.BOLD);
        final Font headerFont = FontFactory.getFont(FontFactory.HELVETICA, 12, Font.BOLD);
        final Font normalFont = FontFactory.getFont(FontFactory.HELVETICA, 12, Font.NORMAL);

        final Document doc = new Document();
        final PdfWriter writer = PdfWriter.getInstance(doc, stream);
        doc.open();
        if (getDocumentNumber() != null) {
            Image image = Image.getInstance(new BarcodeHelper().generateBarcodeImage(getDocumentNumber()), null);
            doc.add(image);
        }

        final Paragraph title = new Paragraph("TEM Coversheet", titleFont);
        doc.add(title);

        final Paragraph faxNumber = new Paragraph("Fax this page to " + SpringContext.getBean(ParameterService.class).getParameterValueAsString(TravelReimbursementDocument.class, FAX_NUMBER), normalFont);
        doc.add(faxNumber);

        final Paragraph header = new Paragraph("", headerFont);
        header.setAlignment(ALIGN_RIGHT);
        header.add("Document Number: " + getDocumentNumber());
        doc.add(header);
        doc.add(getInstructionsParagraph());
        doc.add(getMailtoParagraph());
        doc.add(Chunk.NEWLINE);
        doc.add(getTripInfo());
        doc.add(Chunk.NEWLINE);
        doc.add(getPersonalInfo());
        doc.add(Chunk.NEWLINE);
        doc.add(getExpenses());

        drawAlignmentMarks(writer.getDirectContent());

        doc.close();
        writer.close();
    }
}
