/*
    Copyright 1996-2008 Ariba, Inc.

    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.

    $Id: //ariba/platform/util/core/ariba/util/core/Crypto.java#8 $
*/

package ariba.util.core;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

import ariba.util.log.Log;

/**
    Disclaimer. This is not production security! This is a weak
    attempt to thwart direct or accidental packet sniffing from
    obtaining cleartext passwords. Of course real encryption is the
    solution but at this moment is not achievable due to schedule
    constraints.

    @aribaapi private
*/
public class Crypto implements CryptoInterface
{
    private CryptoChar cryptoChar;

    private boolean passThrough = false;

    public Crypto (Object key)
    {
        this.cryptoChar = new CryptoChar(key.hashCode());
    }

    public Crypto (Object key, boolean enabled)
    {
        this.passThrough = !enabled;
        this.cryptoChar = new CryptoChar(key.hashCode());
    }

    public Object encrypt (Object target)
    {
        if (this.passThrough) {
            return target;
        }
            // Log.runtime.debug("encrypt %s", target);
        if (target instanceof String) {
            return this.encrypt((String)target);
        }
        if (target instanceof Map) {
            return this.encrypt((Map)target);
        }
        if (target instanceof List) {
            return this.encrypt((List)target);
        }
        return target;
    }

    public char encrypt (char ch)
    {
        return this.cryptoChar.encrypt(ch);
    }

    public String encrypt (String string)
    {
        int strLen = string.length();
        char [] charBuffer = new char[strLen];
        string.getChars(0, strLen, charBuffer, 0);
        reverse(charBuffer, strLen);
        for (int idx = 0; idx < strLen; idx++) {
            charBuffer[idx] = this.encrypt(charBuffer[idx]);
        }

        return new String(charBuffer);
    }

    public static char [] reverse (char [] charBuffer, int charBufferLength)
    {
        int halfStrLen = charBufferLength/2;
        for (int idx = 0; idx < halfStrLen; idx++) {
            char temp = charBuffer[idx];
            int revIdx = charBufferLength-idx-1;
            charBuffer[idx] = charBuffer[revIdx];
            charBuffer[revIdx] = temp;
        }
        return charBuffer;
    }


    public Map encrypt (Map map)
    {
        int size = map.size();
        // calling new Map with size zero is a no-no
        Map result = (size>0)?MapUtil.map(size):MapUtil.map();
        Iterator e = map.keySet().iterator();
        while (e.hasNext()) {
            Object key = e.next();
            Object value = map.get(key);
            result.put(this.encrypt(key), this.encrypt(value));
        }
        return result;
    }

    public List encrypt (List vector)
    {
        int size = vector.size();
        // calling ListUtil.list with size zero is a no-no
        List result = (size>0)?ListUtil.list(size):ListUtil.list();
        for (int idx = 0; idx < size; idx++) {
            Object value = vector.get(idx);
            result.add(this.encrypt(value));
        }
        return result;
    }

    public Object decrypt (Object target)
    {
        if (this.passThrough) {
            return target;
        }
            // Log.runtime.debug("decrypt %s", target);
        if (target instanceof String) {
            return this.decrypt((String)target);
        }
        if (target instanceof Map) {
            return this.decrypt((Map)target);
        }
        if (target instanceof List) {
            return this.decrypt((List)target);
        }
        return target;
    }

    public char decrypt (char ch)
    {
        return this.cryptoChar.decrypt(ch);
    }

    public String decrypt (String string)
    {
        int strLen = string.length();
        char [] charBuffer = new char[strLen];
        string.getChars(0, strLen, charBuffer, 0);
        reverse(charBuffer, strLen);
        for (int idx = 0; idx < strLen; idx++) {
            charBuffer[idx] = this.decrypt(charBuffer[idx]);
        }

        return new String(charBuffer);
    }

    public Map decrypt (Map map)
    {
        int size = map.size();
        // calling new Map with size zero is a no-no
        Map result = (size>0)?MapUtil.map(size):MapUtil.map();
        Iterator e = map.keySet().iterator();
        while (e.hasNext()) {
            Object key = e.next();
            Object value = map.get(key);
            result.put(this.decrypt(key), this.decrypt(value));
        }
        return result;
    }

    public List decrypt (List vector)
    {
        int size = vector.size();
        // calling ListUtil.list with size zero is a no-no
        List result = (size>0)?ListUtil.list(size):ListUtil.list();
        for (int idx = 0; idx < size; idx++) {
            Object value = vector.get(idx);
            result.add(this.decrypt(value));
        }
        return result;
    }

    /*
    public static void main (String [] args)
    {
        test(0,(char)0xFFFF);
        test(0,'a');
        for (int idx = 0; idx < 16; idx++) {
            bigtest(idx, args);
        }
    }

    public static void test (int key, char input)
    {
        PrintStream out = System.out;
        Random random = new Random();
        out.println("input is " + input);
        Crypto captCrunch = new Crypto(key);
        char x = captCrunch.encrypt(input);
        out.println("crypted is " + x);
        char y = captCrunch.decrypt(x);
        out.println("decrypted is " + y);
    }

    public static String formatArray (String [] args)
    {
        String output = Constants.EmptyString;
        for (int idx = 0; idx < args.length; idx++) {
            output =
                output +
                " arg[" +
                idx +
                "] = " +
                args[idx];

        }
        return output;
    }

    public static void bigtest (int key, String [] args)
    {
        PrintStream out = System.out;
        out.println("Crypting " + formatArray(args));

        Crypto captCrunch = new Crypto(key);

        List vector = ListUtil.list();
        for (int idx = 0; idx<args.length - 1; idx+=2) {
            vector.add(Constants.getInteger(idx));

            Map map = new Map();
            map.put(args[idx], args[idx+1]);
            vector.add(map);
        }


        out.println("List - precrypt: " + vector);
        vector = captCrunch.encrypt(vector);
        out.println("List - prostcrypt: " + vector);
        vector = (List) captCrunch.decrypt(vector);
        out.println("List - prostdecrypt: " + vector);
    }
    */
}

class CryptoChar
{
    static final int CharBitLength = 16;
    static final int MinShift = 5;

    int encryptLowerCharShiftCount;
    int encryptUpperCharShiftCount;

    char encryptLowerCharMask;
    char encryptUpperCharMask;

    char decryptLowerCharMask;
    char decryptUpperCharMask;

    int decryptLowerCharShiftCount;
    int decryptUpperCharShiftCount;

    public CryptoChar (int key)
    {
        this.encryptLowerCharShiftCount =
            Math.abs(key % (CharBitLength-MinShift)) +
            MinShift;

        Log.util.debug("CryptoChar - key: %s", this.encryptLowerCharShiftCount);
        this.init();
    }

    private void init ()
    {
        this.encryptUpperCharShiftCount =
            CharBitLength - this.encryptLowerCharShiftCount;

        this.encryptLowerCharMask = (char)
            (0xFFFF >>> this.encryptLowerCharShiftCount);
        this.encryptUpperCharMask = (char)
            (0xFFFF << this.encryptUpperCharShiftCount);

        this.decryptLowerCharMask = (char)
            (this.encryptUpperCharMask >>> this.encryptUpperCharShiftCount);
        this.decryptUpperCharMask = (char)
            (this.encryptLowerCharMask << this.encryptLowerCharShiftCount);

        this.decryptLowerCharShiftCount = this.encryptUpperCharShiftCount;
        this.decryptUpperCharShiftCount = this.encryptLowerCharShiftCount;
    }

    public char encrypt (char val)
    {
        char lowerShifted = (char)
            (val << this.encryptLowerCharShiftCount);
        char upperShifted = (char)
            ((val & this.encryptUpperCharMask) >>>
             this.encryptUpperCharShiftCount);
        char retVal = (char)(lowerShifted | upperShifted);
        return retVal;
    }

    /*
    public void dumpChar (char x)
    {
        PrintStream out = System.out;
        byte upper = (byte)(x << CharBitLength);
        byte lower = (byte)x;
        out.println(this.dumpByte(upper)+this.dumpByte(lower));
    }

    public String dumpByte (byte x)
    {
        String output = Constants.EmptyString;
        for (int idx = 0; idx < 16; idx++) {
            boolean on = ((x & 0x80)==0);
            if (on) {
                output = output + "1";
            }
            else {
                output = output + "0";
            }
            x = (byte)(x << 1);
        }
        return output;
    }
    */

    public char decrypt (char val)
    {
        char lowerShifted = (char)
            (val << this.decryptLowerCharShiftCount);
        char upperShifted = (char)
            ((val & this.decryptUpperCharMask) >>>
             this.decryptUpperCharShiftCount);
        char retVal = (char)(lowerShifted | upperShifted);
        return retVal;
    }
}

