/*
 * I-BS.pl PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 * Copyright (c) 2012, I-BS.pl and/or its affiliates. All rights reserved.
 */
package net.ranides.assira.math;

import java.util.Arrays;
import java.util.Locale;
import java.util.Random;
import net.ranides.assira.text.StringEncoder;
import net.ranides.assira.trace.LoggerUtils;
import static org.junit.Assert.*;
import org.junit.Ignore;
import org.junit.Test;

/**
 * Uwaga - #testCycles musi mieć dostępne 512 MB pamięci. Patrz: "pom.xml"
 * @author ranides
 */
@SuppressWarnings("PMD.SystemPrintln")
public class LFSRTest {
    
    private static final int MAX_CYCLE = 64 * 1000 * 1000;
    
    private BS buffer;
    
    public LFSRTest() {
    }
    
    @Test
    public void testSession() {
        Random generator = new LFSR(0x1);
        byte[] bytes = new byte[16];
        
        String[] expected = new String[]{
            "0100000001000080010000c000000060",
            "010000b0010000d80000006c010000b6",
            "010000db0000806d0100c0b6010060db",
            "0000b06d0100d8b601006cdb0000b66d",
            "0100dbb601806ddb00c0b66d0160dbb6",
            "01b06ddb00d8b66d016cdbb600b66d5b",
            "00dbb62d806ddb16c1b66d8b60dbb645",
            "b16ddba2d8b66d516cdbb628b66d5b14",
            "dcb62d8a6edb16c5b76d8be2dcb645f1",
            "6ddba278b66d513cdcb6289e6e5b14cf",
            "b72d8ae7dc16c5f36d8be279b645f13c",
            "dca2789e6d513c4fb6289e275b14cf13",
            "2d8ae70917c5f3848be2794245f13c21",
            "a3789e90523c4fc8289e276414cf1332",
            "8be70999c5f3844ce2794226f13c2113",
            "789e90093d4fc8849e276442cf133221"
        };
        
        for(int i=0; i<16; i++) {
            generator.nextBytes(bytes);
            assertEquals(expected[i], StringEncoder.bytesToHex(bytes).toLowerCase(Locale.ROOT));
        }
    }
    
    @Ignore("manual slow")
    @Test
    public void testCycles() {
        
        assertEquals( 115135, testCycle(new Random(0), 0) );
        assertEquals( 94482, testCycle(new Random(17), 17) );
        assertEquals( 78192, testCycle(new Random(32), 32) );
        assertEquals( 192385, testCycle(new Random(40), 40) );
        assertEquals( 66466, testCycle(new Random(170), 170) );
        assertEquals( 112008, testCycle(new Random(197), 197) );
        
        assertEquals( 1, testCycle(new LFSR(0), 0) );
        assertEquals( MAX_CYCLE, testCycle(new LFSR(17), 17) );
        assertEquals( MAX_CYCLE, testCycle(new LFSR(32), 32) );
        assertEquals( MAX_CYCLE, testCycle(new LFSR(40), 40) );
        assertEquals( MAX_CYCLE, testCycle(new LFSR(170), 170) );
        assertEquals( MAX_CYCLE, testCycle(new LFSR(197), 197) );
    }
    
    private int testCycle(Random generator, int seed) {
        // Intel Core i3 540 @ 3.07GHz
        // Czas przeszukiwania 320 mln / 164 s
        // 
        // czyli 1 seed na około 40 minut
        resetBuffer();
        
        buffer.clear(true);
        for (int i = 0; i<MAX_CYCLE; i++) {
            if( buffer.exists( generator.nextInt() ) ) {
                LoggerUtils.dump("cycle length for seed %d = %d.\n", seed, i);
                return i;
            }
        }
        
        buffer.clear(false);
        for (int i = 0; i<MAX_CYCLE; i++) {
            if( buffer.exists( generator.nextInt() ) ) {
                LoggerUtils.dump("cycle length for seed %d = %d.\n", seed, i);
                return i;
            }
        }
        
        LoggerUtils.dump("cycle length for seed %d > %d.\n", seed, MAX_CYCLE);
        return MAX_CYCLE;
    }
    
    private BS resetBuffer() {
        if( buffer == null) {
            buffer = new BS();
        }
        return buffer;
    }
    
    private static class BS {
        
        private char words[] = new char[128 * 1024 * 1024];
        private boolean pmode;
        
        public boolean exists(int value) {
            return pmode ? existsP(value) : existsN(value);
        }
        
        private boolean existsP(int value) {
            if(value < 0) {
                return false;
            } 
            int offset = value>>4;
            int mask = 1 << (value & 0xf);
            if(0 != (words[offset] & mask)) {
                return true;
            }
            words[offset] |= mask;
            return false;
        }
        
        private boolean existsN(int value) {
            if(value > 0) {
                return false;
            } else {
                return existsP(-value);
            }
        }
        
        void clear(boolean pmode) {
            Arrays.fill(words, 0, words.length, '\0');
            this.pmode = pmode;
        }
    }
}
