/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.io.uri.impl;

import static net.ranides.assira.io.uri.URIFlags.*;
import static net.ranides.assira.junit.NewAssert.*;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;

import net.ranides.assira.collection.prototype.GenericMapBuilder;
import net.ranides.assira.junit.JCategories;
import org.junit.Ignore;
import org.junit.Test;

import net.ranides.assira.collection.prototype.OpenGenericMap;
import net.ranides.assira.credentials.UserStoreList;
import net.ranides.assira.credentials.UserProperty;
import net.ranides.assira.io.uri.URIFlags;
import net.ranides.assira.io.uri.URIHandle;
import net.ranides.assira.io.uri.URIResolver;
import net.ranides.assira.io.uri.URITime;
import net.ranides.assira.text.Charsets;
import net.ranides.assira.text.IOStrings;
import net.ranides.assira.trace.ExceptionUtils;
import org.junit.experimental.categories.Category;

/**
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
@Category(JCategories.Online.class)
public class CFTPHandlerTest {
    
    private static final String TEXT_1 = "Źrebię patrzy na słońce.";
    
    private static final String TEXT_2 = "infov1";

    private final URIResolver re = new URIResolver()
        .register(CFTPHandler.class)
        .users(new UserStoreList().add(GenericMapBuilder.<String>hash()
            .put(UserProperty.ACCOUNT, "user")
            .put(UserProperty.LOGIN, "f12357_assira_ftp_test")
            .put(UserProperty.PASSWORD, "TTYKYbg25ijGxlnYBGfN")
            .map()
        ));

    /**
     * Niepoprawne logowanie do FTP mydevil może zablokować IP maszyny.
     * https://www.mydevil.net/odblokuj.html
     */
    @Ignore("manual")
    @Category(JCategories.Slow.class)
    @Test
    public void testLoginError() {
        assertThrows(IOException.class, ()-> {
            re.resolve("ftp://nop@ftp.ranides.net/dir").flags();
        });
        assertThrows(IOException.class, ()-> {
            re.resolve("ftp://nop:551@ftp.ranides.net/dir").flags();
        });
    }

    @Test
    public void testTime() throws IOException {
        try(URIHandle handle = re.resolve("ftp://user@ftp.ranides.net/dir")) {
            assertEquals("2017-03-06T15:21:21Z", handle.time(URITime.MODIFIED).toString());
            assertThrows(IOException.class, ()->{
                handle.time(URITime.CREATED);
            });
        }
        try(URIHandle handle = re.resolve("ftp://user@ftp.ranides.net/info.txt")) {
            assertEquals("2016-10-24T15:07:13Z", handle.time(URITime.MODIFIED).toString());
            assertThrows(IOException.class, ()->{
                handle.time(URITime.CREATED);
            });
        }
    }

    @Test
    public void testSize() throws IOException {
        try(URIHandle handle = re.resolve("ftp://user@ftp.ranides.net/dir")) {
            assertEquals(6, handle.size());
        }
        try(URIHandle handle = re.resolve("ftp://user@ftp.ranides.net/dir/other")) {
            assertEquals(2, handle.size());
        }
        try(URIHandle handle = re.resolve("ftp://user@ftp.ranides.net/dir/empty")) {
            assertEquals(0, handle.size());
        }
        try(URIHandle handle = re.resolve("ftp://user@ftp.ranides.net/readme.txt")) {
            assertEquals(28, handle.size());
        }
    }

    @Test
    public void testFlags() throws IOException {
        URIHandle handle1 = re.resolve("ftp://user@ftp.ranides.net/");
        URIHandle handle2 = re.resolve("ftp://user@ftp.ranides.net/dir/data");
        URIHandle handle3 = re.resolve("ftp://user@ftp.ranides.net/dir/other/content.txt");
        URIHandle handle4 = re.resolve("ftp://user@ftp.ranides.net/nop");

        assertEquals(URIFlags.collect(EXISTS, DIR, SIZE), handle1.flags());
        assertEquals(URIFlags.collect(EXISTS, DIR, SIZE), handle2.flags());
        assertEquals(URIFlags.collect(EXISTS, FILE, BINARY, WRITABLE, READABLE, SIZE, SEEK), handle3.flags());
        assertEquals(URIFlags.collect(), handle4.flags());

        handle1.close();
        handle2.close();
        handle3.close();
        handle4.close();
    }

    @Test
    public void testScan() throws URISyntaxException, IOException {

        String[] exp1 = {
            "ftp://f12357_assira_ftp_test:TTYKYbg25ijGxlnYBGfN@ftp.ranides.net/dir/data",
            "ftp://f12357_assira_ftp_test:TTYKYbg25ijGxlnYBGfN@ftp.ranides.net/dir/empty",
            "ftp://f12357_assira_ftp_test:TTYKYbg25ijGxlnYBGfN@ftp.ranides.net/dir/file.bin",
            "ftp://f12357_assira_ftp_test:TTYKYbg25ijGxlnYBGfN@ftp.ranides.net/dir/first.txt",
            "ftp://f12357_assira_ftp_test:TTYKYbg25ijGxlnYBGfN@ftp.ranides.net/dir/other",
            "ftp://f12357_assira_ftp_test:TTYKYbg25ijGxlnYBGfN@ftp.ranides.net/dir/sec.log"
        };

        String[] exp2 = {
            "ftp://user@ftp.ranides.net/.ftpquota",
            "ftp://user@ftp.ranides.net/dir",
            "ftp://user@ftp.ranides.net/info.txt",
            "ftp://user@ftp.ranides.net/readme.txt"
        };

        String[] exp3 = {
            "ftp://user@ftp.ranides.net/dir/data",
            "ftp://user@ftp.ranides.net/dir/empty",
            "ftp://user@ftp.ranides.net/dir/file.bin",
            "ftp://user@ftp.ranides.net/dir/first.txt",
            "ftp://user@ftp.ranides.net/dir/other",
            "ftp://user@ftp.ranides.net/dir/sec.log"
        };

        String[] exp4 = {
            "ftp://user@ftp.ranides.net/dir/other/content.txt",
            "ftp://user@ftp.ranides.net/dir/other/list.txt",
        };

        URI u1 = new URI("ftp://f12357_assira_ftp_test:TTYKYbg25ijGxlnYBGfN@ftp.ranides.net/dir");
        URI u2 = new URI("ftp://user@ftp.ranides.net");
        URI u3 = new URI("ftp://user@ftp.ranides.net/dir/");
        URI u4 = new URI("ftp://user@ftp.ranides.net/dir/other");


        try(URIHandle handle = re.resolve(u1)) {
            assertEquals("ftp://f12357_assira_ftp_test:TTYKYbg25ijGxlnYBGfN@ftp.ranides.net", handle.parent().uri().toString());
            assertArrayEquals(exp1, handle.scan().map(e -> asName(e)).sort().array(String[]::new));
        }

        try(URIHandle handle = re.resolve(u2)) {
            assertEquals("ftp://user@ftp.ranides.net", handle.parent().uri().toString());
            assertArrayEquals(exp2, handle.scan().map(e -> asName(e)).sort().array(String[]::new));
        }

        try(URIHandle handle = re.resolve(u3)) {
            assertEquals("ftp://user@ftp.ranides.net", handle.parent().uri().toString());
            assertEquals("ftp://user@ftp.ranides.net", handle.parent().parent().uri().toString());
            assertArrayEquals(exp3, handle.scan().map(e -> asName(e)).sort().array(String[]::new));
        }

        try(URIHandle handle = re.resolve(u4)) {
            assertEquals("ftp://user@ftp.ranides.net/dir", handle.parent().uri().toString());
            assertEquals("ftp://user@ftp.ranides.net", handle.parent().parent().uri().toString());
            assertArrayEquals(exp4, handle.scan().map(e -> asName(e)).sort().array(String[]::new));
        }

        try(URIHandle handle = re.resolve(u2)) {
            assertEquals(TEXT_1, IOStrings.read(handle.scan().list().get(3).reader()));
        }

    }

    private static String asName(URIHandle handle) {
        return handle.uri().toString();
    }

    @Test
    public void testUser() throws URISyntaxException, IOException {
        URI u1 = new URI("ftp://user@ftp.ranides.net/readme.txt");
        
        URIHandle handle1 = re.resolve(u1);
        assertEquals(TEXT_1, IOStrings.read(handle1.reader()));

        URI u2 = new URI("ftp://user@ftp.ranides.net/info.txt");
        URIHandle handle2 = re.resolve(u2);
        assertEquals(TEXT_2, IOStrings.read(handle2.reader()));
        
        handle1.close();
        handle2.close();
    }

    @Test
    public void testTimeout() throws URISyntaxException, IOException {

        URI u1 = new URI("ftp://user@ftp.ranides.net/readme.txt?timeout=750");
        URI u2 = new URI("ftp://user@ftp.ranides.net/readme.txt?timeout=1");
        URI u3 = new URI("ftp://user@ftp.ranides.net/readme.txt?timeout=2");
        URI u4 = new URI("ftp://user@ftp.ranides.net/readme.txt?timeout=800");


        URIHandle handle1 = re.resolve(u1);
        URIHandle handle2 = re.resolve(u2);

        // we will use timeout from "u1"
        assertEquals(TEXT_1, IOStrings.read(handle1.reader()));
        // we are already connected
        // we won't use any timeout
        assertEquals(TEXT_1, IOStrings.read(handle2.reader()));

        handle1.close();
        handle2.close();

        URIHandle handle3 = re.resolve(u3);
        URIHandle handle4 = re.resolve(u4);

        // we will use timeout from "u3"
        assertThrows(IOException.class, () -> {
            assertEquals(TEXT_1, IOStrings.read(handle3.reader()));
        });

        // we are not connected yet
        // we will use timeout from "u4"
        assertEquals(TEXT_1, IOStrings.read(handle4.reader()));
    }
    
    @Test
    public void testCopy() throws URISyntaxException, IOException {

        URI u1 = new URI("ftp://user@ftp.ranides.net/readme.txt");
        URI u2 = new URI("ftp://user@ftp.ranides.net/out-1.txt");
        String s3 = "ftp://user@ftp.ranides.net/out-2.txt?charset=windows-1250";
        try (URIHandle target = re.resolve(u2)) {
            
            assertFalse(target.exists());
            
            try (URIHandle source = re.resolve(u1)) {
                source.copy(target);
            }
            try (URIHandle source = re.resolve(u1)) {
                source.copy(s3);
            }
            
            assertEquals(TEXT_1, IOStrings.read(target.reader()));
            assertTrue(target.exists());
            
            target.delete();
            assertFalse(target.exists());
            
            try(URIHandle u3 = re.resolve(new URI(s3))) {
                assertNotEquals(TEXT_1, IOStrings.read(u3.reader()));
                assertEquals(cast(TEXT_1, Charsets.UTF8 ,Charsets.PL_WINDOWS), IOStrings.read(u3.reader()));
                u3.delete();
            }
        }
        
    }

    @Test
    public void testMove() throws URISyntaxException, IOException {

        URI u1 = new URI("ftp://user@ftp.ranides.net/readme.txt");
        URI u2 = new URI("ftp://user@ftp.ranides.net/source.txt");
        URI u3 = new URI("ftp://user@ftp.ranides.net/target.txt");
        URIHandle h1 = re.resolve(u1);
        URIHandle h2 = re.resolve(u2);
        URIHandle h3 = re.resolve(u3);

        try {

            assertFalse(h2.exists());
            assertFalse(h3.exists());

            h1.copy(h2);
            h2.move(h3);

            assertFalse(h2.exists());
            assertTrue(h3.exists());
            assertEquals(TEXT_1, IOStrings.read(h3.reader()));

        } finally {
            h2.delete();
            h3.delete();

            h1.close();
            h2.close();
            h3.close();
        }

//        try (URIHandle target = re.resolve(u2)) {
//
//            assertFalse(target.exists());
//
//            try (URIHandle source = re.resolve(u1)) {
//                source.copy(target);
//            }
//            try (URIHandle source = re.resolve(u1)) {
//                source.copy(s3);
//            }
//
//            assertEquals(TEXT_1, IOStrings.read(target.reader()));
//            assertTrue(target.exists());
//
//            target.delete();
//            assertFalse(target.exists());
//
//            try(URIHandle u3 = re.resolve(new URI(s3))) {
//                assertNotEquals(TEXT_1, IOStrings.read(u3.reader()));
//                assertEquals(cast(TEXT_1, Charsets.UTF8 ,Charsets.PL_WINDOWS), IOStrings.read(u3.reader()));
//                u3.delete();
//            }
//        }

    }
    
    private static String cast(String text, Charset source, Charset target) {
        return new String(text.getBytes(source), target);
    }

}
