/**
 * License Agreement.
 *
 * Rich Faces - Natural Ajax for Java Server Faces (JSF)
 *
 * Copyright (C) 2007 Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */
package org.richfaces.resource;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.TimeZone;

import javax.faces.application.Resource;
import javax.faces.context.FacesContext;

import org.jboss.test.faces.AbstractFacesTest;

/**
 * @author Nick Belaevski
 * @since 4.0
 */
public class CachedResourceImplTest extends AbstractFacesTest {
    /**
     *
     */
    private static final String CACHE_CONTROL = "Cache-Control";
    /**
     *
     */
    private static final String EXPIRES = "Expires";
    /**
     *
     */
    private static final String LAST_MODIFIED = "Last-Modified";

    @Override
    public void setUp() throws Exception {
        super.setUp();
        setupFacesRequest();
    }

    @Override
    public void tearDown() throws Exception {
        super.tearDown();
    }

    private CachedResourceImpl createCachedResource() throws IOException {
        return createCachedResource(createTestHeaders());
    }

    private CachedResourceImpl createCachedResource(Map<String, String> headers) throws IOException {
        return createCachedResource(headers, null);
    }

    private CachedResourceImpl createCachedResource(Map<String, String> headers, InputStream stream) throws IOException {

        MockHeadersResourceImpl mockResource = new MockHeadersResourceImpl(headers, stream);
        CachedResourceImpl cachedResource = new MockCachedResourceImpl();

        cachedResource.initialize(mockResource);

        return cachedResource;
    }

    private Map<String, String> createTestHeaders() {
        Map<String, String> headers = new HashMap<String, String>();

        headers.put("ETag", "W/\"123\"");
        headers.put(LAST_MODIFIED, "Tue, 21 Jul 2009 12:45:09 GMT");
        headers.put(EXPIRES, "Tue, 28 Jul 2009 12:45:09 GMT");
        headers.put(CACHE_CONTROL, "public, max-age=86400");

        return headers;
    }

    private Calendar createBaseDateCalendar() {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));

        calendar.clear();
        calendar.set(2009, Calendar.JULY, 21, 12, 45, 9);

        return calendar;
    }

    public void testInitialize() throws Exception {
        CachedResourceImpl cachedResource = createCachedResource();
        Calendar calendar = createBaseDateCalendar();
        Date baseDate = calendar.getTime();

        assertEquals("W/\"123\"", cachedResource.getEntityTag(facesContext));
        assertEquals(baseDate, cachedResource.getLastModified(facesContext));
        assertEquals(createTestHeaders(), cachedResource.getResponseHeaders());
        assertEquals("image/png", cachedResource.getContentType());

        Date expired = cachedResource.getExpired(facesContext);

        assertNotNull(expired);
        assertTrue(expired.after(baseDate));
        calendar.add(Calendar.DATE, 1);
        assertFalse(calendar.getTime().before(expired));
    }

    public void testInitializeExpires() throws Exception {
        Map<String, String> headers = createTestHeaders();

        headers.remove(CACHE_CONTROL);

        CachedResourceImpl cachedResource = createCachedResource(headers);
        Date expired = cachedResource.getExpired(facesContext);

        assertNotNull(expired);

        Calendar calendar = createBaseDateCalendar();

        assertTrue(expired.after(calendar.getTime()));
        calendar.add(Calendar.DATE, 1);
        assertFalse(expired.before(calendar.getTime()));
        calendar.add(Calendar.DATE, 6);
        assertTrue(expired.before(calendar.getTime()));
    }

    public void testInitializeMaxAge() throws Exception {
        Map<String, String> headers = createTestHeaders();

        headers.remove(EXPIRES);

        CachedResourceImpl cachedResource = createCachedResource(headers);
        Date expired = cachedResource.getExpired(facesContext);

        assertNotNull(expired);

        Calendar calendar = createBaseDateCalendar();

        assertTrue(expired.after(calendar.getTime()));
        calendar.add(Calendar.DATE, 1);
        assertTrue(expired.before(calendar.getTime()));
    }

    public void testInitializeSMaxAge() throws Exception {
        Map<String, String> headers = createTestHeaders();

        headers.put(CACHE_CONTROL, "public, max-age=86400, s-maxage=172800");

        CachedResourceImpl cachedResource = createCachedResource(headers);
        Date expired = cachedResource.getExpired(facesContext);

        assertNotNull(expired);

        Calendar calendar = createBaseDateCalendar();

        assertTrue(expired.after(calendar.getTime()));
        calendar.add(Calendar.DATE, 1);
        assertFalse(expired.before(calendar.getTime()));
        calendar.add(Calendar.DATE, 1);
        assertTrue(expired.before(calendar.getTime()));
    }

    public void testGetStream() throws Exception {
        ByteArrayInputStream testStream = new ByteArrayInputStream("test".getBytes("US-ASCII"));
        CachedResourceImpl cachedResource = createCachedResource(createTestHeaders(), testStream);
        InputStream stream = cachedResource.getInputStream();

        assertNotNull(stream);

        BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "US-ASCII"));

        assertEquals("test", reader.readLine());
        assertNull(reader.readLine());
        reader.close();
    }

    public void testGetBigStream() throws Exception {
        byte[] bs = new byte[127123];

        new Random().nextBytes(bs);

        ByteArrayInputStream bigStream = new ByteArrayInputStream(bs);
        CachedResourceImpl cachedResource = createCachedResource(createTestHeaders(), bigStream);
        InputStream stream = cachedResource.getInputStream();
        byte[] testBs = new byte[bs.length];

        assertEquals(testBs.length, stream.read(testBs));
        assertTrue(Arrays.equals(testBs, bs));
        assertEquals(-1, stream.read());
        stream.close();
    }

    public void testCacheable() throws Exception {
        CachedResourceImpl cachedResource = createCachedResource();

        assertTrue(cachedResource.isCacheable(facesContext));
    }

    public void testDefaults() throws Exception {
        CachedResourceImpl cachedResource = createCachedResource();

        try {
            cachedResource.getURL();
            fail();
        } catch (UnsupportedOperationException e) {

            // ok
        }

        try {
            cachedResource.getRequestPath();
            fail();
        } catch (UnsupportedOperationException e) {

            // ok
        }
    }

    public void testUserAgentNeedsUpdate() throws Exception {
        CachedResourceImpl cachedResource = createCachedResource();

        this.connection.addRequestHeaders(Collections.singletonMap("If-Modified-Since", "Tue, 21 Jul 2009 14:45:09 GMT"));
        assertFalse(cachedResource.userAgentNeedsUpdate(facesContext));
    }

    public void testUserAgentNeedsUpdate2() throws Exception {
        CachedResourceImpl cachedResource = createCachedResource();

        this.connection.addRequestHeaders(Collections.singletonMap("If-Modified-Since", "Tue, 21 Jul 2009 09:45:09 GMT"));
        assertTrue(cachedResource.userAgentNeedsUpdate(facesContext));
    }

    private class MockCachedResourceImpl extends CachedResourceImpl {
        private final long currentTime;

        public MockCachedResourceImpl() {
            super();
            this.currentTime = createBaseDateCalendar().getTimeInMillis();
        }

        @Override
        long getCurrentTime() {
            return currentTime;
        }
    }

    private class MockHeadersResourceImpl extends Resource {
        private Map<String, String> headers;
        private InputStream stream;

        public MockHeadersResourceImpl(Map<String, String> headers, InputStream stream) {
            super();
            setResourceName(getClass().getName());
            setContentType("image/png");
            this.headers = headers;
            this.stream = stream != null ? stream : new ByteArrayInputStream(new byte[0]);
        }

        @Override
        public Map<String, String> getResponseHeaders() {
            return headers;
        }

        @Override
        public InputStream getInputStream() throws IOException {
            return stream;
        }

        @Override
        public String getRequestPath() {
            throw new UnsupportedOperationException();
        }

        @Override
        public URL getURL() {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean userAgentNeedsUpdate(FacesContext context) {
            throw new UnsupportedOperationException();
        }
    }

    ;
}
