/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * 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 com.android.tradefed.util;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import com.android.tradefed.util.IEmail.Message;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

@RunWith(JUnit4.class)
public class EmailTest {
    private TestEmail mEmail = null;

    /**
     * A mock class to let us capture the data that is passed to the process on stdin
     */
    class TestProcess extends Process {
        OutputStream mOutputStream = null;

        TestProcess(OutputStream stream) {
            mOutputStream = stream;
        }

        @Override
        public OutputStream getOutputStream() {
            return mOutputStream;
        }

        // Add stubs for all the abstract methods
        class StubInputStream extends InputStream {
            @Override
            public int read() {return 0;}
        }

        @Override
        public void destroy() {}
        @Override
        public int exitValue() {return 0;}
        @Override
        public int waitFor() {return 0;}
        @Override
        public InputStream getInputStream() {
            return new StubInputStream();
        }
        @Override
        public InputStream getErrorStream() {
            return new StubInputStream();
        }
    }

    /**
     * A mock class that doesn't actually run anything, but instead returns a mock Process to
     * capture the data that would be passed _to_ the process on stdin.  Java's naming of these
     * methods is insane.
     */
    class TestEmail extends Email {
        ByteArrayOutputStream mOutputStream = null;

        TestEmail() {
            mOutputStream = new ByteArrayOutputStream();
        }

        public String getMailerData() {
            return mOutputStream.toString();
        }

        @Override
        Process run(String[] cmd) {
            return new TestProcess(mOutputStream);
        }
    }

    @Before
    public void setUp() throws Exception {

        mEmail = new TestEmail();
    }

    /**
     * Ensure that IllegalArgumentException is thrown when a message without a destination address
     * is sent. Note that the address is not validated in any way (it could even be null)
     */
    @Test
    public void testSendInval_destination() throws IOException {
        Message msg = new Message();
        msg.setSubject("subject");
        msg.setBody("body");

        try {
            mEmail.send(msg);
            fail("IllegalArgumentException not thrown");
        } catch (IllegalArgumentException e) {
            // expected
        }
    }

    /** Ensure that IllegalArgumentException is thrown when a message without a subject is sent. */
    @Test
    public void testSendInval_subject() throws IOException {
        Message msg = new Message("dest@ination.com", null, "body");
        try {
            mEmail.send(msg);
            fail("IllegalArgumentException not thrown");
        } catch (IllegalArgumentException e) {
            // expected
        }
    }

    /** Ensure that IllegalArgumentException is thrown when a message without a body is sent. */
    @Test
    public void testSendInval_body() throws IOException {
        Message msg = new Message("dest@ination.com", "subject", null);
        try {
            mEmail.send(msg);
            fail("IllegalArgumentException not thrown");
        } catch (IllegalArgumentException e) {
            // expected
        }
    }

    /** Ensure that the email body is passed correctly to the mailer program's standard input */
    @Test
    public void testSend_simple() throws IOException {
        final Message msg = new Message("dest@ination.com", "subject", "body");
        msg.setSender("or@igin.com");
        mEmail.send(msg);

        final Map<String, String> headers = new HashMap<String, String>();
        final String body = extractBody(mEmail.getMailerData(), headers);

        assertEquals("body", body);
        assertEquals("or@igin.com", headers.get("From"));
        assertEquals("dest@ination.com", headers.get("To"));
        assertEquals("subject", headers.get("Subject"));
        assertEquals(Message.PLAIN, headers.get("Content-type"));
        assertEquals(4, headers.size());
    }

    /** Make sure that the HTML flag is passed along correctly */
    @Test
    public void testSend_htmlEmail() throws IOException {
        final String expectedBody = "<html><body>le body</body></html>";
        final Message msg = new Message("dest@ination.com", "subject", expectedBody);
        msg.setHtml(true);
        mEmail.send(msg);

        final Map<String, String> headers = new HashMap<String, String>();
        final String body = extractBody(mEmail.getMailerData(), headers);

        assertEquals(expectedBody, body);
        assertEquals(Message.HTML, headers.get("Content-type"));
    }

    /**
     * Ensure that the email is sent correctly even if the message has an empty (but present)
     * subject
     */
    @Test
    public void testSend_emptySubject() throws IOException {
        Message msg = new Message("dest@ination.com", "", "body");
        mEmail.send(msg);
        assertTrue("body".equals(extractBody(mEmail.getMailerData())));
    }

    /**
     * Ensure that the email is sent correctly even if the message has an empty (but present) body.
     */
    @Test
    public void testSend_emptyBody() throws IOException {
        Message msg = new Message("dest@ination.com", "subject", "");
        mEmail.send(msg);
        assertTrue("".equals(extractBody(mEmail.getMailerData())));
    }

    /**
     * A short functional test to send an email somewhere.  Intended to be manually enabled with
     * appropriate email addresses to verify that Email functionality is working after changes.
     * Not enabled by default because the particular addresses to use will depend on the environment
     */
    @SuppressWarnings("unused")
    @Ignore
    public void _manual_testFuncSend() throws IOException {
        final String sender = null;
        final String[] to = {"RECIPIENT"};
        final String[] cc = {};
        final String[] bcc = {};

        final String subject = "This is a TF test email";
        final String body = "<html><body><h1>What do we want?!</h1><h2>Time travel!</h2>" +
                "When do we want it?<span style=\"display:block;color:blue;font-weight:bold\">" +
                "it's irrelevant!</span></body></html>";
        final String contentType = Message.HTML;

        final Message msg = new Message();
        msg.setSubject(subject);
        msg.setBody(body);
        msg.setContentType(contentType);
        if (sender != null) {
            msg.setSender(sender);
        }
        for (String addr : to) {
            msg.addTo(addr);
        }
        for (String addr : cc) {
            msg.addCc(addr);
        }
        for (String addr : bcc) {
            msg.addBcc(addr);
        }

        final IEmail mailer = new Email();
        mailer.send(msg);
    }

    private String extractBody(String data) {
        return extractBody(data, null);
    }

    /**
     * Helper function that takes a full email and splits it into the headers and the body.
     * Optionally returns the headers via the second arg
     */
    private String extractBody(String data, Map<String, String> headers) {
        final String[] pieces = data.split(Email.CRLF + Email.CRLF, 2);
        if (headers != null) {
            for (String header : pieces[0].split(Email.CRLF)) {
                final String[] halves = header.split(": ", 2);
                final String key = halves[0];
                final String val = halves.length == 2 ? halves[1] : null;

                headers.put(key, val);
            }
        }

        if (pieces.length < 2) {
            return null;
        } else {
            return pieces[1];
        }
    }
}

