////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Datacard Corporation.  All Rights Reserved.
////////////////////////////////////////////////////////////////////////////////
package common_java;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterJob;
import java.awt.geom.AffineTransform;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.print.PrintService;
import javax.print.PrintServiceLookup;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.Sides;

import javax.print.attribute.standard.PrinterResolution;
import java.awt.print.Paper;



public class JavaPrint implements Printable {

    private final double POINTS_PER_INCH = 72;
    
    private String  mPrinterName;
    private boolean mDuplex;
    private boolean mPortrait;
    private boolean mDoMagstripe;
    private String  mBlockingFront;
    private String  mBlockingBack;
    private boolean mDoSampleText;
    private boolean mDoTactileImpressFront = false;
    private boolean mDoTactileImpressBack  = false;
    private int     mTactileImpressLocation = 0;	// FYI - 0 is not a valid tactile impress location
    private boolean mEmboss  = false;

    public JavaPrint(
            String  printerName, 
            boolean duplex, 
            boolean portrait,
            boolean doMagstripe, 
            String  blockingFront, 
            String  blockingBack,
            boolean doSampleText) 
    {
        mPrinterName     = printerName;
        mDuplex          = duplex;
        mPortrait        = portrait;
        mDoMagstripe     = doMagstripe;
        mBlockingFront   = blockingFront;
        mBlockingBack    = blockingBack;
        mDoSampleText    = doSampleText;
    }
    
   
    public JavaPrint(
            String  printerName, 
            boolean duplex, 
            boolean portrait,
            boolean doMagstripe, 
            String  blockingFront, 
            String  blockingBack,
            boolean doSampleText,
            boolean doTactileImpressFront, 
            boolean doTactileImpressBack,
            int     tactileImpressLocation) 
    {
        mPrinterName     = printerName;
        mDuplex          = duplex;
        mPortrait        = portrait;
        mDoMagstripe     = doMagstripe;
        mBlockingFront   = blockingFront;
        mBlockingBack    = blockingBack;
        mDoSampleText    = doSampleText;
        mDoTactileImpressFront   = doTactileImpressFront;
        mDoTactileImpressBack    = doTactileImpressBack;
        mTactileImpressLocation  = tactileImpressLocation;
    }

    public JavaPrint(
            String  printerName, 
            boolean duplex, 
            boolean portrait,
            boolean doMagstripe, 
            String  blockingFront, 
            String  blockingBack,
            boolean doSampleText,
            boolean doTactileImpressFront, 
            boolean doTactileImpressBack,
            int     tactileImpressLocation,
            boolean emboss) 
    {
        mPrinterName     = printerName;
        mDuplex          = duplex;
        mPortrait        = portrait;
        mDoMagstripe     = doMagstripe;
        mBlockingFront   = blockingFront;
        mBlockingBack    = blockingBack;
        mDoSampleText    = doSampleText;
        mDoTactileImpressFront   = doTactileImpressFront;
        mDoTactileImpressBack    = doTactileImpressBack;
        mTactileImpressLocation  = tactileImpressLocation;
        mEmboss					 = emboss;
        mDoSampleText            = doSampleText;
    }

    private PrintService GetPrinterService() {
        PrintService[] printServices = PrintServiceLookup.lookupPrintServices(
                null, null);

        for (PrintService printer : printServices) {
            if (printer.getName().compareToIgnoreCase(mPrinterName) == 0) {
                System.out.println("Found Printer: " + printer.getName());
                return printer;
            }
        }
        System.out.println("Did not match any printer name as : " + mPrinterName);
        return null;
    }

    public void PrintDemo() {

        PrintService printService = GetPrinterService();
        if (printService == null) {
            return;
        }

        try {

        	// java.awt.print.PrinterJob
        	//The PrinterJob class is the principal class that controls printing. An application 
        	//calls methods in this class to set up a job,optionally to invoke a print dialog 
        	//with the user, and then to print the pages of the job.

            PrinterJob printJob = PrinterJob.getPrinterJob();
            printJob.setPrintService(printService);
            
            // using 300 DPI
            PrinterResolution pr = new PrinterResolution(300, 300,  PrinterResolution.DPI);
            
            // Set the printable class to this one since we are implementing the
            // Printable interface:
            PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();
            aset.add(pr);
             
            // FYI - somtimes the incoming paper size is width = 11.0", height = 8.5"
            //     - the page size should be the size of a card size is 3.375" x 2.125"
            PageFormat pf = printJob.getPageFormat(null);
            Paper paper = pf.getPaper();
            
        	double paperWidth = paper.getWidth()   / 72.0;  // inches
        	double paperHeight = paper.getHeight() / 72.0;	// inches
        	
        	// looking for an invalid "original" paper size
        	if (paperWidth < 2.0 || paperWidth > 3.5 || paperHeight < 2.0 || paperHeight > 3.5) {
                System.out.println("  original paper width = " + paperWidth + ", height = " + paperHeight + " (in inches)");
        	}
            
            paper.setSize(2.125 * 72, 3.375 * 72);
            paper.setImageableArea( 0.0, 0.0, paper.getWidth(), paper.getHeight() );                      
            if (mPortrait) {
                pf.setOrientation(PageFormat.PORTRAIT);
            } else {
            	pf.setOrientation(PageFormat.LANDSCAPE);
            }

            pf.setPaper(paper);

            if (mDuplex) {
                // FYI - using "Sides.DUPLEX" results a "flip on long side" (i.e. back side of card is upside down)
            	//     - objects on the back side of the card are individually rotated in the "drawImage()" and 
            	//       "rotateText180()" methods.
            	// It would be nice to use Sides.TWO_SIDED_SHORT_EDGE to resolve the issue of the back side of
            	// the card being "upside down"
				// However, Sides.TWO_SIDED_SHORT_EDGE has no effect.
            	// - Perhaps in newer versions of Java (or a newer JNA) "Sides.TWO_SIDED_SHORT_EDGE" may 
            	//   have the desired effect.  This would alleviate the need to individually rotated in the 
            	//   "drawImage()" and "rotateText180()" methods.
                aset.add(Sides.DUPLEX);
                
                mDuplex = true;
            } else {
                aset.add(Sides.ONE_SIDED);
            }

            printJob.setPrintable(this, pf);
            printJob.print(aset);

        } catch (Exception PrintException) {
            PrintException.printStackTrace();
        }
    }

    /**
     * method: WriteEmbossEscapes()
     */
    private void WriteEmbossEscapes(Graphics2D g2d) {
   
      final String emboss1Escape = "~EM%1;301;860;Font 11111";
      final String emboss2Escape = "~EM%2;1600;860;222222";
      final String emboss3Escape = "~EM%3;301;1460;333333";
      final String emboss4Escape = "~EM%4;301;1180;444444";
      final String emboss5Escape = "~EM%5;301;690;555555";
      final String emboss6Escape = "~EM%6;1600;690;666666";
      final String emboss7Escape = "~EM%7;301;650;777777";
      final String emboss8Escape = "~EM%8;301;1000;888888";
      final String emboss9Escape = "~EM%9;301;1050;999999";
      final String emboss10Escape = "~EM%10;1600;1050;10 10 10";
       
      g2d.drawString(emboss1Escape, 0, 0);
      g2d.drawString(emboss2Escape, 0, 0);
      g2d.drawString(emboss3Escape, 0, 0);
      g2d.drawString(emboss4Escape, 0, 0);
      g2d.drawString(emboss5Escape, 0, 0);
      g2d.drawString(emboss6Escape, 0, 0);
      g2d.drawString(emboss7Escape, 0, 0);
      g2d.drawString(emboss8Escape, 0, 0);
      g2d.drawString(emboss9Escape, 0, 0);
      g2d.drawString(emboss10Escape, 0, 0);
  }
    	
    
    
    /**
     * Method: print
     * <p>
     *
     * This class is responsible for rendering a page using the provided
     * parameters. The result will be a grid where each cell is one half inch
     * square.
     *
     * @param g a value of type Graphics
     * @param pageFormat a value of type PageFormat
     * @param pageNumber a value of type int
     * @return an int error code.
     */
    @Override
    public int print(Graphics g, PageFormat pageFormat, int pageNumber) {
        int imgStartX = 10;
        int imgStartY = 10;

        // --- Validate the page number, we only print max two pages
        if (pageNumber > 1) {
            System.out.println("  Print job has been sent to spooler.");
            return Printable.NO_SUCH_PAGE;
        }

        // --- Translate the origin to be (0,0)
        Graphics2D graphics2D = (Graphics2D) g;
        graphics2D.setFont(new Font(null, Font.PLAIN, 8));

        final int printerDPI = 300;
        graphics2D.translate(pageFormat.getImageableX(), pageFormat.getImageableY());

        double pageWidth = pageFormat.getWidth() * printerDPI / POINTS_PER_INCH;
        double pageHeight = pageFormat.getHeight() * printerDPI / POINTS_PER_INCH;
        
       if (pageNumber == 0 && !mDoSampleText) {
            graphics2D.setColor(Color.black);
            Line2D.Double line = new Line2D.Double();

            // --- Print the vertical lines
            for (int lineIndex = 0; lineIndex < pageWidth; lineIndex += POINTS_PER_INCH / 2) {
                line.setLine(lineIndex, 0, lineIndex, pageHeight);
                graphics2D.draw(line);
                graphics2D.drawString("V" + Integer.toString(lineIndex), lineIndex, 20);
            }

            // --- Print the horizontal lines
            for (int lineIndex = 0; lineIndex < pageHeight; lineIndex += POINTS_PER_INCH / 2) {
                line.setLine(0, lineIndex, pageWidth, lineIndex);
                graphics2D.draw(line);
                graphics2D.drawString("H" + Integer.toString(lineIndex), 0, lineIndex);
            }

            // draw a color bitmap
		    drawImage(graphics2D, pageFormat, "images/color.jpg", imgStartX, imgStartY, (int) pageWidth, (int) pageHeight, pageNumber);

		    graphics2D.drawString("Front side", (int) 60, (int) 100);
            if (mBlockingFront != null)
            {
                printBlocking(graphics2D, pageNumber);
            }
            
            if (mDoTactileImpressFront || mDoTactileImpressBack)
            {
                // tactile impress
                WriteTactileImpressEscapes(graphics2D, mDoTactileImpressFront, mDoTactileImpressBack, mTactileImpressLocation);
            }

            return (Printable.PAGE_EXISTS);
        } else if (pageNumber == 0 && mDoSampleText) {
        	graphics2D.setColor(Color.black);
            String sampleTextFront = String.format("Sample printing on %s", mPrinterName); 
            if (mEmboss) {
            	graphics2D.drawString(sampleTextFront, (int) 50, (int) 50);
                WriteEmbossEscapes(graphics2D);
	        } else {
	        	graphics2D.drawString(sampleTextFront, (int) 50, (int) 100);
	        }
            return (Printable.PAGE_EXISTS);
        } else if (pageNumber == 1 && mDuplex && !mDoSampleText) {
            imgStartX = 72;
            imgStartY = 72;

            graphics2D = (Graphics2D) g;
            graphics2D.setColor(Color.black);

            // back side escape encode
            if (mDoMagstripe) {
                WriteMagstripeEscapes(graphics2D);
            }
            
            // draw a 1 bit-per-pixel bitmap to the KPanel:
            drawImage(graphics2D, pageFormat, "images/House_1bpp.png", imgStartX,
                    imgStartY, (int) pageWidth, (int) pageHeight, pageNumber);

            // if this is NOT portrait, need to rotate the string on the back side of the card.
            if( mPortrait ) {
            	graphics2D.drawString("back side ",  (int) 72, (int) 44);
            } 
            else {
                rotateText180(graphics2D, (int) 72, (int) 44, "back side ");
            }
                       
            if (mBlockingBack != null)
            {
                printBlocking(graphics2D, pageNumber);
            }

            return (Printable.PAGE_EXISTS);
        }else if (pageNumber == 1 && mDuplex && mDoSampleText) {
            graphics2D.setColor(Color.black);
            String sampleTextBack = String.format("Sample back side printing on %s", mPrinterName); 
          	rotateText180(graphics2D, (int) 200, (int) 100, sampleTextBack);
            return (Printable.PAGE_EXISTS);
        } else {
            return (NO_SUCH_PAGE);
        }
    }

    private void WriteMagstripeEscapes(Graphics2D g2d) {
        final String track1Escape = "~1ABC 123";
        final String track2Escape = "~2456";
        final String track3Escape = "~3789";

        g2d.drawString(track1Escape, 0, 0);
        g2d.drawString(track2Escape, 0, 0);
        g2d.drawString(track3Escape, 0, 0);
    }

    private void WriteTactileImpressEscapes(Graphics2D g2d, boolean tactileImpressFront, boolean tactileImpressBack, int tactileImpressLocation) {
    	// example ~IT%F1?
        String strTactile = "~IT%";

        if (tactileImpressFront) {
        	strTactile += "F";
        } else if  (tactileImpressBack) {
        	strTactile += "B";
        }
    
        // append "location"
        strTactile += tactileImpressLocation;
        
        // append closing "?"
        strTactile += "?";
        g2d.drawString(strTactile,  0, 0);
        
    }

    private void printBlocking(Graphics2D g2d, int pageNumber) {
        switch ((pageNumber == 0) ? mBlockingFront : mBlockingBack) {
        case "all":    blockPrintAll(g2d);
                       break;
        case "chip":   blockPrintSmartcard(g2d);
                       break;
        case "magJIS": blockPrintMagJIS(g2d, pageNumber);
                       break;
        case "mag2":   blockPrintMag2(g2d, pageNumber);
                       break;
        case "mag3":   blockPrintMag3(g2d, pageNumber);
                       break;
        case "custom": blockPrintCustom(g2d, pageNumber);
                       break;
        }
    }
    
    // Topcoat and print blocking for custom
    // A 'Topcoat Add' escape will force topcoat OFF for the entire card side.
    // Units are millimeters; portrait basis; top left width height:
    
    // Topcoat entire card, no print blocking, as per usage
    private void blockPrintAll(Graphics2D g2d) {
        ;
    }
    
    // Topcoat and print blocking for smart card
    private void blockPrintSmartcard(Graphics2D g2d) {
        g2d.drawString("~TR% 8 23.6 13 14?", 0, 30);
        g2d.drawString("~PB% 8 23.6 13 14?", 0, 30);
    }
    
    // Topcoat and print blocking for magstripe JIS
    private void blockPrintMagJIS(Graphics2D g2d, int pageNumber) {
        if (pageNumber == 0) {	// front
            g2d.drawString("~TR% 0 39.1 9 120?", 0, 0);
            g2d.drawString("~PB% 0 39.1 9 120?", 0, 0);
        }
        else {
            g2d.drawString("~TR% 0 5.7 9 120?", 0, 0);
            g2d.drawString("~PB% 0 5.7 9 120?", 0, 0);
        }
    }

    // Topcoat and print blocking for magstripe 2-track
    private void blockPrintMag2(Graphics g2d, int pageNumber) {
        if (pageNumber == 0) {	// front
	        g2d.drawString("~TR% 0 39.0 11 87?", 0, 0);
	        g2d.drawString("~PB% 0 39.0 11 87?", 0, 0);
        }
        else {
	        g2d.drawString("~TR% 0 4.0 11 87?", 0, 0);
	        g2d.drawString("~PB% 0 4.0 11 87?", 0, 0);
        }
    }
    
    // Topcoat and print blocking for magstripe 3-track
    private void blockPrintMag3(Graphics g2d, int pageNumber) {
        if (pageNumber == 0) {	// front
        	g2d.drawString("~TR% 0 35.5 14.5 87?", 0, 0);
            g2d.drawString("~PB% 0 35.5 14.5 87?", 0, 0);
        }
        else {
        	g2d.drawString("~TR% 0 4.0 14.5 87?", 0, 0);
            g2d.drawString("~PB% 0 4.0 14.5 87?", 0, 0);
        }
    }
    

    private void blockPrintCustom(Graphics g2d, int pageNumber) {
        if (pageNumber == 0) {
            // Add a rectangle one inch down; two inches wide; 1 cm high.
            // Also add a 7mm x 7mm square in the lower right of card.
            g2d.drawString("~TA% 0 18 10 50.8; 61 6.5 7 7", 0, 0);
            g2d.drawString("~PB% 19 0 54 3?", 0, 0);
        } else if (pageNumber == 1) {
            g2d.drawString("~TA% 10 8 20 50.8", 0, 0);
            g2d.drawString("~PB% 19 0 54 3?", 0, 0);
        }
    }

    private void drawImage(Graphics2D graphics2D, PageFormat pageFormat,
            String aFileName, int x, int y, int pageWidth, int pageHeight, int pageNumber) {

        BufferedImage img = getSystemImage(aFileName);

        if (img == null) {
            return;
        }
        
        // Because "Sides.DUPLEX" performs a "flip on long edge" the image on the back of card will be upside down.
		// If this image is for the back side of the card, rotate the image       
        if (pageNumber == 1 && mPortrait == false) {	// back side of card
        	img = rotateImage180(img);
        }

        int w = img.getWidth();		// in pixels
        int h = img.getHeight();    // in pixels
        int destW = (int) (w * 0.3) + x;	// why is the image width/height being multipled by 0.3????
        int destH = (int) (h * 0.3) + y;

        if (w > pageWidth) {
            destW = pageWidth;
        }

        if (h > pageHeight) {
            destH = pageHeight;
        }

        graphics2D.drawImage(img, x, y, destW, destH, 0, 0, w, h, null);
    }

    private BufferedImage getSystemImage(String filename) {
        if ((filename == null) || (filename.length() == 0)) {
            return null;
        }

        URL url = ClassLoader.getSystemResource(filename);
        if (url == null) {
            System.out.println("  Unable to locate file: " + filename);
            return null;
        }

        try {
        	BufferedImage bufImage = ImageIO.read(ClassLoader.getSystemResourceAsStream(filename));
        	if (bufImage.getWidth() == 0 || bufImage.getHeight() == 0) {
        		System.out.println("  image width / Height = 0");
        		return null;
        	}
        	return bufImage;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return null;
        }
    }
    
    private static void rotateText180(Graphics2D g2d, int x, int y, String text)
    {
    	g2d.translate(x, y);
    	g2d.rotate(Math.toRadians(180));
    	g2d.drawString(text,  0,  0);
    	g2d.rotate(Math.toRadians(180));
    	g2d.translate(-x, -y);    	
    }
    
    
    private static BufferedImage rotateImage180(BufferedImage image)
    {
        if (image == null)
            return null;

        float x = image.getWidth()  / 2.0f;
        float y = image.getHeight() / 2.0f;
        
        return rotateImage(image, 180, x, y);
    }
    
    private static BufferedImage rotateImage(BufferedImage image, int angle, float x, float y)
    {
        int newWidth  = (angle == 180) ? image.getWidth()  : image.getHeight();
        int newHeight = (angle == 180) ? image.getHeight() : image.getWidth();
        
        BufferedImage newImage = new BufferedImage(newWidth, newHeight,
                                                   BufferedImage.TYPE_INT_ARGB);

        double radians = 2 * Math.PI * angle / 360;
        AffineTransform tx = new AffineTransform();
        tx.rotate(radians, x, y);
        
        return applyTransform(image, newImage, tx);
    }
    
    private static BufferedImage applyTransform(BufferedImage sourceImage,
            BufferedImage destImage,
            AffineTransform tx)
	{
		Graphics2D g2 = (Graphics2D) destImage.createGraphics();
		g2.transform(tx);
		g2.drawImage(sourceImage, null, 0, 0);
		g2.dispose();
		
		return destImage;
	}
    
}
