/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2003-2008 University of Maryland
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.Iterator;

import edu.umd.cs.findbugs.ba.SignatureParser;
import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;

/**
 * @author pugh
 */
public class Obfuscate {

    final static String HASH_SEED = SystemProperties.getProperty("hashSeed", "");

    public static String hashData(String in) {
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("SHA-1");
            byte[] hash = md.digest((HASH_SEED + in).getBytes("UTF-8"));
            return String.format("%040x", new BigInteger(1, hash));
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String hashFilename(String fileName) {
        int lastDot = fileName.lastIndexOf('.');
        if (lastDot >= 0) {
            String suffix = fileName.substring(lastDot);
            return hashData(fileName.substring(0, lastDot)) + suffix;
        }
        return hashData(fileName);
    }

    public static String hashClass(@DottedClassName String className) {
        if (className.startsWith("java"))
            return className;
        return "obfuscated.H" + hashData(className);
    }

    public static String hashSignature(String signature) {
        char firstChar = signature.charAt(0);
        switch (firstChar) {
        case '[':
            return '[' + hashSignature(signature.substring(1));
        case 'V':
        case 'Z':

        case 'B':
        case 'S':
        case 'C':
        case 'I':
        case 'J':
        case 'D':
        case 'F':
            if (signature.length() == 1)
                return signature;
            throw new IllegalArgumentException("bad signature: " + signature);
        case 'L':
            if (!signature.endsWith(";"))
                throw new IllegalArgumentException("bad signature: " + signature);
            return hashFieldSignature(signature);
        default:
            throw new IllegalArgumentException("bad signature: " + signature);
        }
    }

    public static String hashFieldSignature(String signature) {
        signature = signature.substring(1, signature.length() - 1);
        if (!signature.startsWith("java"))
                signature = "obfuscated/H" + hashData(signature);
        return "L" + signature + ";";
    }
    
    public static String hashMethodSignature(String signature) {
        SignatureParser parser = new SignatureParser(signature);
        StringBuilder buf = new StringBuilder("(");
        for (Iterator<String> i = parser.parameterSignatureIterator(); i.hasNext();) {
            String param = i.next();
            buf.append(hashSignature(param));
        }
        buf.append(")");
        buf.append(hashSignature(parser.getReturnTypeSignature()));
        return buf.toString();

    }

    static MethodAnnotation obfuscate(MethodAnnotation m) {
        String className = m.getClassName();
        if (className.startsWith("java"))
            return m;

        String methodName = m.getMethodName();
        String methodSignature = m.getMethodSignature();

        if (methodName.equals("hashCode") && methodSignature.equals("()I") || methodName.equals("equals")
                && methodSignature.equals("(Ljava/lang/Object;)Z") || methodName.equals("compareTo")
                && methodSignature.equals("(Ljava/lang/Object;)I") || methodName.equals("<init>")
                || methodName.equals("<clinit>")) {
            // don't need to obfuscate method name
        } else {
            methodName = hashData(methodName);
        }

        MethodAnnotation result = new MethodAnnotation(hashClass(className), methodName, hashMethodSignature(methodSignature),
                m.isStatic());
        result.setDescription(m.getDescription());
        return result;
    }

    static FieldAnnotation obfuscate(FieldAnnotation m) {
        FieldAnnotation result = new FieldAnnotation(hashClass(m.getClassName()), hashData(m.getFieldName()),
                hashSignature(m.getFieldSignature()), m.isStatic());
        result.setDescription(m.getDescription());
        return result;

    }
    

    static ClassAnnotation obfuscate(ClassAnnotation m) {
        ClassAnnotation result = new ClassAnnotation(hashClass(m.getClassName()));
        result.setDescription(m.getDescription());
        return result;
    }

    static TypeAnnotation obfuscate(TypeAnnotation m) {
        TypeAnnotation result = new TypeAnnotation(hashSignature(m.getTypeDescriptor()));
        result.setDescription(m.getDescription());
        return result;

    }

    static IntAnnotation obfuscate(IntAnnotation m) {
        IntAnnotation result = new IntAnnotation(m.getValue());
        result.setDescription(m.getDescription());
        return result;

    }

    static StringAnnotation obfuscate(StringAnnotation m) {
        StringAnnotation result = new StringAnnotation("obfuscated: " + hashData(m.getValue()));
        result.setDescription(m.getDescription());
        return result;

    }

    static SourceLineAnnotation obfuscate(SourceLineAnnotation m) {
        SourceLineAnnotation result = new SourceLineAnnotation(hashClass(m.getClassName()), hashFilename(m.getSourceFile()),
                m.getStartLine(), m.getEndLine(), m.getStartBytecode(), m.getEndBytecode());
        result.setDescription(m.getDescription());
        return result;

    }

    static LocalVariableAnnotation obfuscate(LocalVariableAnnotation m) {

        LocalVariableAnnotation result = new LocalVariableAnnotation(hashData(m.getName()), m.getRegister(), m.getPC());
        result.setDescription(m.getDescription());
        return result;

    }

    public static BugInstance obfuscate(BugInstance b) {
        final BugInstance result = new BugInstance(b.getType(), b.getPriority());
        BugAnnotationVisitor visitor = new BugAnnotationVisitor() {

            public void visitTypeAnnotation(TypeAnnotation typeAnnotation) {
                result.add(obfuscate(typeAnnotation));

            }

            public void visitStringAnnotation(StringAnnotation stringAnnotation) {
                result.add(obfuscate(stringAnnotation));

            }

            public void visitSourceLineAnnotation(SourceLineAnnotation sourceLineAnnotation) {
                result.add(obfuscate(sourceLineAnnotation));

            }

            public void visitMethodAnnotation(MethodAnnotation methodAnnotation) {
                result.add(obfuscate(methodAnnotation));

            }

            public void visitLocalVariableAnnotation(LocalVariableAnnotation fieldAnnotation) {
                result.add(obfuscate(fieldAnnotation));

            }

            public void visitIntAnnotation(IntAnnotation fieldAnnotation) {
                result.add(obfuscate(fieldAnnotation));

            }

            public void visitFieldAnnotation(FieldAnnotation fieldAnnotation) {
                result.add(obfuscate(fieldAnnotation));

            }

            public void visitClassAnnotation(ClassAnnotation classAnnotation) {
                result.add(obfuscate(classAnnotation));

            }
        };
        for (BugAnnotation a : b.getAnnotations())
            a.accept(visitor);
        result.setOldInstanceHash(hashData(b.getInstanceHash()));
        result.setHistory(b);
        return result;
    }

}
