/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.camel.component.printer;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Map;

import javax.print.Doc;
import javax.print.DocFlavor;
import javax.print.DocPrintJob;
import javax.print.PrintService;
import javax.print.PrintServiceLookup;
import javax.print.attribute.Attribute;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.Media;
import javax.print.attribute.standard.MediaSizeName;
import javax.print.attribute.standard.MediaTray;
import javax.print.attribute.standard.Sides;

import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.apache.camel.util.IOHelper;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class PrinterPrintTest extends CamelTestSupport {

    @Before
    public void setup() {
        setupJavaPrint();
    }

    @Override
    public boolean isUseRouteBuilder() {
        return false;
    }

    // Check if there is an awt library
    private boolean isAwtHeadless() {
        return Boolean.getBoolean("java.awt.headless");
    }

    private void sendFile() throws Exception {
        template.send("direct:start", new Processor() {
            public void process(Exchange exchange) throws Exception {
                // Read from an input stream
                InputStream is = IOHelper.buffered(new FileInputStream("src/test/resources/test.txt"));

                byte buffer[] = new byte[is.available()];
                int n = is.available();
                for (int i = 0; i < n; i++) {
                    buffer[i] = (byte)is.read();
                }

                is.close();
                // Set the property of the charset encoding
                exchange.setProperty(Exchange.CHARSET_NAME, "UTF-8");
                Message in = exchange.getIn();
                in.setBody(buffer);
            }
        });
    }

    private void sendGIF() throws Exception {
        template.send("direct:start", new Processor() {
            public void process(Exchange exchange) throws Exception {
                // Read from an input stream
                InputStream is = IOHelper.buffered(new FileInputStream("src/test/resources/asf-logo.gif"));

                byte buffer[] = new byte[is.available()];
                int n = is.available();
                for (int i = 0; i < n; i++) {
                    buffer[i] = (byte)is.read();
                }

                is.close();
                // Set the property of the charset encoding
                exchange.setProperty(Exchange.CHARSET_NAME, "UTF-8");
                Message in = exchange.getIn();
                in.setBody(buffer);
            }
        });
    }

    private void sendJPEG() throws Exception {
        template.send("direct:start", new Processor() {
            public void process(Exchange exchange) throws Exception {
                // Read from an input stream
                InputStream is = IOHelper.buffered(new FileInputStream("src/test/resources/asf-logo.JPG"));

                byte buffer[] = new byte[is.available()];
                int n = is.available();
                for (int i = 0; i < n; i++) {
                    buffer[i] = (byte)is.read();
                }

                is.close();

                // Set the property of the charset encoding
                exchange.setProperty(Exchange.CHARSET_NAME, "UTF-8");
                Message in = exchange.getIn();
                in.setBody(buffer);
            }
        });
    }

    @Test
    @Ignore
    public void testSendingFileToPrinter() throws Exception {
        if (isAwtHeadless()) {
            return;
        }
        context.addRoutes(new RouteBuilder() {
            public void configure() {
                from("direct:start").
                        to("lpr://localhost/default?copies=1&flavor=DocFlavor.BYTE_ARRAY&mimeType=AUTOSENSE&mediaSize=na-letter&sides=one-sided&sendToPrinter=false");
            }
        });
        context.start();

        sendFile();
    }

    @Test
    @Ignore
    public void testSendingGIFToPrinter() throws Exception {
        if (isAwtHeadless()) {
            return;
        }
        context.addRoutes(new RouteBuilder() {
            public void configure() {
                from("direct:start").
                        to("lpr://localhost/default?flavor=DocFlavor.INPUT_STREAM&mimeType=GIF&mediaSize=na-letter&sides=one-sided&sendToPrinter=false");
            }
        });
        context.start();

        sendGIF();
    }

    @Test
    @Ignore
    public void testSendingJPEGToPrinter() throws Exception {
        if (isAwtHeadless()) {
            return;
        }
        context.addRoutes(new RouteBuilder() {
            public void configure() {
                from("direct:start").to("lpr://localhost/default?copies=2&flavor=DocFlavor.INPUT_STREAM"
                        + "&mimeType=JPEG&mediaSize=na-letter&sides=one-sided&sendToPrinter=false");
            }
        });
        context.start();

        sendJPEG();
    }

    /**
     * Test for resolution of bug CAMEL-3446.
     * Not specifying mediaSize nor sides attributes make it use
     * default values when starting the route.
     */
    @Test
    @Ignore
    public void testDefaultPrinterConfiguration() throws Exception {
        if (isAwtHeadless()) {
            return;
        }
        context.addRoutes(new RouteBuilder() {
            public void configure() {
                from("direct:start").to("lpr://localhost/default?sendToPrinter=false");
            }
        });
        context.start();
    }
    
    @Test
    public void moreThanOneLprEndpoint() throws Exception {

        if (isAwtHeadless()) {
            return;
        }
        
        int numberOfPrintservicesBefore = PrintServiceLookup.lookupPrintServices(null, null).length;

        // setup javax.print 
        PrintService ps1 = mock(PrintService.class);
        when(ps1.getName()).thenReturn("printer1");
        when(ps1.isDocFlavorSupported(any(DocFlavor.class))).thenReturn(Boolean.TRUE);
        PrintService ps2 = mock(PrintService.class);
        when(ps2.getName()).thenReturn("printer2");
        boolean res1 = PrintServiceLookup.registerService(ps1);
        assertTrue("PrintService #1 should be registered.", res1);
        boolean res2 = PrintServiceLookup.registerService(ps2);
        assertTrue("PrintService #2 should be registered.", res2);
        PrintService[] pss = PrintServiceLookup.lookupPrintServices(null, null);
        assertEquals("lookup should report two PrintServices.", numberOfPrintservicesBefore + 2, pss.length);

        DocPrintJob job1 = mock(DocPrintJob.class);
        when(ps1.createPrintJob()).thenReturn(job1);
        
        context.addRoutes(new RouteBuilder() {

            public void configure() {
                from("direct:start1").to("lpr://localhost/printer1?sendToPrinter=true");
                from("direct:start2").to("lpr://localhost/printer2?sendToPrinter=false");
            }
        });
        context.start();

        // Are there two different PrintConfigurations?
        Map<String, Endpoint> epm = context().getEndpointMap();
        assertEquals("Four endpoints", 4, epm.size());
        Endpoint lp1 = null;
        Endpoint lp2 = null;
        for (Map.Entry<String, Endpoint> ep : epm.entrySet()) {
            if (ep.getKey().contains("printer1")) {
                lp1 = ep.getValue();
            }
            if (ep.getKey().contains("printer2")) {
                lp2 = ep.getValue();
            }
        }
        assertNotNull(lp1);
        assertNotNull(lp2);
        assertEquals("printer1", ((PrinterEndpoint)lp1).getConfig().getPrintername());
        assertEquals("printer2", ((PrinterEndpoint)lp2).getConfig().getPrintername());

        template.sendBody("direct:start1", "Hello Printer 1");

        context.stop();

        verify(job1, times(1)).print(any(Doc.class), any(PrintRequestAttributeSet.class));
    }
    
    @Test
    public void printerNameTest() throws Exception {
        if (isAwtHeadless()) {
            return;
        }
        // setup javax.print 
        PrintService ps1 = mock(PrintService.class);
        when(ps1.getName()).thenReturn("MyPrinter\\\\remote\\printer1");
        when(ps1.isDocFlavorSupported(any(DocFlavor.class))).thenReturn(Boolean.TRUE);
        boolean res1 = PrintServiceLookup.registerService(ps1);
        assertTrue("The Remote PrintService #1 should be registered.", res1);
        DocPrintJob job1 = mock(DocPrintJob.class);
        when(ps1.createPrintJob()).thenReturn(job1);
        
        context.addRoutes(new RouteBuilder() {

            public void configure() {
                from("direct:start1").to("lpr://remote/printer1?sendToPrinter=true");
            }
        });
        context.start();
        
        template.sendBody("direct:start1", "Hello Printer 1");

        context.stop();

        verify(job1, times(1)).print(any(Doc.class), any(PrintRequestAttributeSet.class));
    }
    
    @Test
    public void setJobName() throws Exception {
        if (isAwtHeadless()) {
            return;
        }

        getMockEndpoint("mock:output").setExpectedMessageCount(1);
        context.addRoutes(new RouteBuilder() {
            public void configure() {
                from("direct:start").to("lpr://localhost/default").to("mock:output");
            }
        });
        context.start();
        template.sendBodyAndHeader("direct:start", "Hello Printer", PrinterEndpoint.JOB_NAME, "Test-Job-Name");
        context.stop();
        assertMockEndpointsSatisfied();
    }

    @Test
    public void printToMiddleTray() throws Exception {
        PrinterEndpoint endpoint = new PrinterEndpoint();
        PrinterConfiguration configuration = new PrinterConfiguration();
        configuration.setHostname("localhost");
        configuration.setPort(631);
        configuration.setPrintername("DefaultPrinter");
        configuration.setMediaSizeName(MediaSizeName.ISO_A4);
        configuration.setInternalSides(Sides.ONE_SIDED);
        configuration.setMediaTray("middle");

        PrinterProducer producer = new PrinterProducer(endpoint, configuration);
        producer.start();
        PrinterOperations printerOperations = producer.getPrinterOperations();
        PrintRequestAttributeSet attributeSet = printerOperations.getPrintRequestAttributeSet();

        Attribute attribute = attributeSet.get(javax.print.attribute.standard.Media.class);
        assertNotNull(attribute);
        assertTrue(attribute instanceof MediaTray);
        MediaTray mediaTray = (MediaTray) attribute;
        assertEquals("middle", mediaTray.toString());
    }
    
    protected void setupJavaPrint() {
        // "install" another default printer
        PrintService psDefault = mock(PrintService.class);
        when(psDefault.getName()).thenReturn("DefaultPrinter");
        when(psDefault.isDocFlavorSupported(any(DocFlavor.class))).thenReturn(Boolean.TRUE);
        PrintServiceLookup psLookup = mock(PrintServiceLookup.class);
        when(psLookup.getPrintServices()).thenReturn(new PrintService[]{psDefault});
        when(psLookup.getDefaultPrintService()).thenReturn(psDefault);
        DocPrintJob docPrintJob = mock(DocPrintJob.class);
        when(psDefault.createPrintJob()).thenReturn(docPrintJob);
        MediaTray[] trays = new MediaTray[]{
            MediaTray.TOP,
            MediaTray.MIDDLE,
            MediaTray.BOTTOM
        };
        when(psDefault.getSupportedAttributeValues(Media.class, null, null)).thenReturn(trays);
        PrintServiceLookup.registerServiceProvider(psLookup);
    }
}
