package railo.runtime.crypt;

import railo.commons.lang.StringUtil;

public final class CFMXCompat {

    public static final String ALGORITHM_NAME = "cfmx_compat";


    private String m_Key;
    private int m_LFSR_A = 0x13579bdf;
    private int m_LFSR_B = 0x2468ace0;
    private int m_LFSR_C = 0xfdb97531;
    private int m_Mask_A = 0x80000062;
    private int m_Mask_B = 0x40000020;
    private int m_Mask_C = 0x10000002;
    private int m_Rot0_A = 0x7fffffff;
    private int m_Rot0_B = 0x3fffffff;
    private int m_Rot0_C = 0xfffffff;
    private int m_Rot1_A = 0x80000000;
    private int m_Rot1_B = 0xc0000000;
    private int m_Rot1_C = 0xf0000000;
    

    public byte[] transformString(String key, byte inBytes[]) {
        setKey(key);
        int length = inBytes.length;
        byte outBytes[] = new byte[length];
        for(int i = 0; i < length; i++) {
            outBytes[i] = transformByte(inBytes[i]);
        }
        return outBytes;
    }

    private byte transformByte(byte target) {
        byte crypto = 0;
        int b = m_LFSR_B & 1;
        int c = m_LFSR_C & 1;
        for(int i = 0; i < 8; i++) {
            if(0 != (m_LFSR_A & 1)) {
                m_LFSR_A = m_LFSR_A ^ m_Mask_A >>> 1 | m_Rot1_A;
                if(0 != (m_LFSR_B & 1)){
                    m_LFSR_B = m_LFSR_B ^ m_Mask_B >>> 1 | m_Rot1_B;
                    b = 1;
                } 
                else{
                    m_LFSR_B = m_LFSR_B >>> 1 & m_Rot0_B;
                    b = 0;
                }
            } 
            else{
                m_LFSR_A = m_LFSR_A >>> 1 & m_Rot0_A;
                if(0 != (m_LFSR_C & 1)) {
                    m_LFSR_C = m_LFSR_C ^ m_Mask_C >>> 1 | m_Rot1_C;
                    c = 1;
                } 
                else{
                    m_LFSR_C = m_LFSR_C >>> 1 & m_Rot0_C;
                    c = 0;
                }
            }
            crypto = (byte)(crypto << 1 | b ^ c);
        }
        target ^= crypto;
        return target;
    }

    private void setKey(String key) {
        int i = 0;
        m_Key = key;
        if(StringUtil.isEmpty(key)) key = "Default Seed";
        char Seed[] = new char[key.length() >= 12 ? key.length() : 12];
        m_Key.getChars(0, m_Key.length(), Seed, 0);
        int originalLength = m_Key.length();
        for(i = 0; originalLength + i < 12; i++)
            Seed[originalLength + i] = Seed[i];

        for(i = 0; i < 4; i++) {
            m_LFSR_A = (m_LFSR_A <<= 8) | Seed[i + 4];
            m_LFSR_B = (m_LFSR_B <<= 8) | Seed[i + 4];
            m_LFSR_C = (m_LFSR_C <<= 8) | Seed[i + 4];
        }
        if(0 == m_LFSR_A)m_LFSR_A = 0x13579bdf;
        if(0 == m_LFSR_B)m_LFSR_B = 0x2468ace0;
        if(0 == m_LFSR_C)m_LFSR_C = 0xfdb97531;
    }


    /**
     * returns true if the passed value is empty or is CFMX_COMPAT
     */
    public static boolean isCfmxCompat( String algorithm ) {

        if ( StringUtil.isEmpty( algorithm, true ) )
            return true;

        return algorithm.equalsIgnoreCase( CFMXCompat.ALGORITHM_NAME );
    }
}
