/*
 * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package info.wso2.scim2.compliance.pdf;

import info.wso2.scim2.compliance.entities.Result;
import info.wso2.scim2.compliance.entities.TestResult;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.encoding.WinAnsiEncoding;


import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;

/**
 * This class handle the PDF report generation for the test suite.
 */
public class PDFGenerator {

    private static PDDocument document = null;

    /**
     * Init report generation.
     * @param finalResults
     */
    private static void init(Result finalResults){
        document = new PDDocument();
        if (finalResults.getErrorMessage() != ""){
            //Creating a blank page
            PDPage blankPage = new PDPage();
            //Adding the blank page to the document
            document.addPage(blankPage);

        } else {
            for (int i = 0; i < finalResults.getResults().size(); i++) {
                //Creating a blank page
                PDPage blankPage = new PDPage();
                //Adding the blank page to the document
                document.addPage(blankPage);
            }
        }
    }

    /**
     * Method to generate the report in PDF format.
     * @param finalResults
     * @param fullPath
     * @return
     * @throws IOException
     */
    public static String GeneratePDFResults(Result finalResults, String fullPath) throws IOException {
        init(finalResults);
        int pageNo = 0;
        for (TestResult testResult : finalResults.getResults()){
            //Retrieving the pages of the document
            PDPage page = document.getPage(pageNo);
            PDPageContentStream contentStream = new PDPageContentStream(document, page);

            PDFont pdfFont = PDType1Font.HELVETICA;
            float fontSize = 10;
            float smallFontSize = 8;
            float leading = 1.5f * fontSize;

            PDRectangle mediabox = page.getMediaBox();
            float margin = 42;
            float width = mediabox.getWidth() - 2 * margin;
            float startX = mediabox.getLowerLeftX() + margin;
            float startY = mediabox.getUpperRightY() - margin;

            ArrayList<String> toServer = new ArrayList<>();
            ArrayList<String> fromServer = new ArrayList<>();
            ArrayList<String> subTests = new ArrayList<>();
            if (testResult.getWire() != null){
                toServer = removeUnsupportedCharacters(testResult.getWire().getToServer());
                fromServer = removeUnsupportedCharacters(testResult.getWire().getFromServer());
                subTests = removeUnsupportedCharacters(testResult.getWire().getTests());
            }

            List testName = getLines(testResult.getName(), fontSize, pdfFont, width);
            List testMessage = getLines(testResult.getMessage(), fontSize, pdfFont, width);
            List testLabel = getLines(testResult.getStatusText(), fontSize, pdfFont, width);
            List responseBody = new ArrayList();
            List requestBody = new ArrayList();
            if(!toServer.isEmpty()){
                requestBody = getLines( toServer.get(toServer.size()-1), fontSize, pdfFont, width);
                toServer.remove(toServer.size()-1);
            }
            if(!fromServer.isEmpty()){
                responseBody = getLines( fromServer.get(fromServer.size()-1), fontSize, pdfFont, width);
                fromServer.remove(fromServer.size()-1);
            }
            List emptyLine = new ArrayList();
            emptyLine.add(" ");

            //Drawing a rectangle
            contentStream.addRect(startX, startY - 5 , width, 1);

            //Begin text printing
            contentStream.fill();
            contentStream.beginText();
            contentStream.setFont(PDType1Font.TIMES_ITALIC, fontSize);
            contentStream.newLineAtOffset(startX, startY);

            contentStream.showText("SCIM 2.0 Compliance Test Suite - Auto Generated Test Report");
            printResult(contentStream, fontSize, pdfFont, leading, startX, startY, emptyLine);
            printResult(contentStream, fontSize, pdfFont, leading, startX, startY, emptyLine);

            contentStream.setFont(PDType1Font.HELVETICA_BOLD_OBLIQUE, fontSize);
            contentStream.showText("Test Case Name : ");
            contentStream.setFont(PDType1Font.COURIER, fontSize);
            printResult(contentStream, fontSize, pdfFont, leading, startX, startY, emptyLine);
            printResult(contentStream, fontSize, pdfFont, leading, startX, startY, testName);

            contentStream.setFont(PDType1Font.HELVETICA_BOLD_OBLIQUE, fontSize);
            contentStream.showText("Test Case Errors : ");
            contentStream.setFont(PDType1Font.COURIER, fontSize);
            printResult(contentStream, fontSize, pdfFont, leading, startX, startY, emptyLine);
            printResult(contentStream, fontSize, pdfFont, leading, startX, startY, testMessage);

            contentStream.setFont(PDType1Font.HELVETICA_BOLD_OBLIQUE, fontSize);
            contentStream.showText("Test Case Status : ");
            contentStream.setFont(PDType1Font.COURIER, fontSize);
            printResult(contentStream, fontSize, pdfFont, leading, startX, startY, emptyLine);
            printResult(contentStream, fontSize, pdfFont, leading, startX, startY, testLabel);

            contentStream.setFont(PDType1Font.HELVETICA_BOLD_OBLIQUE, fontSize);
            contentStream.showText(("To Server : "));
            contentStream.setFont(PDType1Font.COURIER, fontSize);
            printResult(contentStream, fontSize, pdfFont, leading, startX, startY, emptyLine);
            printResult(contentStream, smallFontSize, pdfFont, leading, startX, startY, toServer);
            printResult(contentStream, fontSize, pdfFont, leading, startX, startY, requestBody);

            contentStream.setFont(PDType1Font.HELVETICA_BOLD_OBLIQUE, fontSize);
            contentStream.showText("From Server : ");
            contentStream.setFont(PDType1Font.COURIER, fontSize);
            printResult(contentStream, smallFontSize, pdfFont, leading, startX, startY, fromServer);
            printResult(contentStream, fontSize, pdfFont, leading, startX, startY, responseBody);

            contentStream.setFont(PDType1Font.HELVETICA_BOLD_OBLIQUE, fontSize);
            contentStream.showText("Sub Tests Performed : ");
            contentStream.setFont(PDType1Font.COURIER, fontSize);
            printResult(contentStream, fontSize, pdfFont, leading, startX, startY, emptyLine);
            printResult(contentStream, fontSize, pdfFont, leading, startX, startY, subTests);

            //Ending the content stream
            contentStream.endText();
            contentStream.close();
            pageNo ++;
        }
        //save the document
        Calendar cal = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("HHmmss");
        String url = fullPath + "\\" + sdf.format(cal.getTime())+ ".pdf";
        document.save(new File(url));

        //Closing the document
        document.close();
        return url;

    }

    /**
     * Print the results to PDF.
     * @param contentStream
     * @param fontSize
     * @param pdfFont
     * @param leading
     * @param startX
     * @param startY
     * @param lines
     * @throws IOException
     */
    public static void printResult(PDPageContentStream contentStream, float fontSize,
                                   PDFont pdfFont, float leading, float startX, float startY, List<String> lines)
            throws IOException {
        for (String line : lines) {
            contentStream.showText(line);
            contentStream.newLineAtOffset(0, -leading);
        }
    }

    /**
     * Method to separate lines of the PDF.
     * @param text
     * @param fontSize
     * @param pdfFont
     * @param width
     * @return
     * @throws IOException
     */
    private static List<String> getLines(String text, float fontSize, PDFont pdfFont, float width)
            throws IOException {
        width = width - 150 ;
        java.util.List<String> lines = new ArrayList<String>();
        int lastSpace = -1;
        while (text.length() > 0) {
            int spaceIndex = text.indexOf(' ', lastSpace + 1);
            if (spaceIndex < 0)
                spaceIndex = text.length();
            String subString = text.substring(0, spaceIndex);
            float size = fontSize * pdfFont.getStringWidth(subString) / 1000;
            if (size > width) {
                float requiredSize = (width * 1000)/fontSize;
                int characterSize = getCharacterCount(requiredSize, subString, pdfFont);
                //if (lastSpace < 0)
                lastSpace = characterSize;
                subString = text.substring(0, lastSpace);
                lines.add(subString);
                text = text.substring(lastSpace).trim();
                lastSpace = -1;
            } else if (spaceIndex == text.length()) {
                lines.add(text);
                text = "";
            } else {
                lastSpace = spaceIndex;
            }
        }
        return lines;
    }

    /**
     * This return the character count of a given text.
     * @param requiredSize
     * @param subString
     * @param pdfFont
     * @return
     * @throws IOException
     */
    private static int getCharacterCount(float requiredSize, String subString, PDFont pdfFont) throws IOException {
        double factor = 0.95;
        String string  = subString;
        while (pdfFont.getStringWidth(string) > requiredSize) {
            string = string.substring(0, (int) Math.round(string.length()*factor));
        }
        return string.length();
    }

    /**
     * This removes the unsupported characters from the text.
     * @param test
     * @return
     * @throws IOException
     */
    private static ArrayList<String> removeUnsupportedCharacters(String test) throws IOException {
        ArrayList<String> textToBeShown= new ArrayList<>();
        StringBuilder b = new StringBuilder();
        for (int i = 0; i < test.length(); i++) {
            if (WinAnsiEncoding.INSTANCE.contains(test.charAt(i)) ) {
                b.append(test.charAt(i));
            } else {
                textToBeShown.add(b.toString());
                b = new StringBuilder();
            }
        }
        textToBeShown.add(b.toString());
        return textToBeShown;
    }

}