/*
 * ====================================================================
 * Copyright (c) 2004-2010 TMate Software Ltd.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://svnkit.com/license.html.
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 * ====================================================================
 */
package org.tmatesoft.svn.core.internal.util.jna;

import java.io.UnsupportedEncodingException;

import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.util.SVNLogType;

import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;

/**
 * @author TMate Software Ltd.
 * @version 1.3
 */
class SVNMacOsKeychain {

    private static final int ERR_SEC_ITEM_NOT_FOUND = -25300;

    public static boolean isEnabled() {
        final boolean enabled = Boolean.valueOf(System.getProperty("svnkit.library.osxkeychain.enabled", "true"));
        return enabled && SVNFileUtil.isOSX && JNALibraryLoader.getMacOsSecurityLibrary() != null;
    }

    public static synchronized boolean setPassword(String realm, String userName, char[] password, boolean nonInteractive) throws SVNException {
        final ISVNMacOsSecurityLibrary library = JNALibraryLoader.getMacOsSecurityLibrary();
        if (library == null) {
            return false;
        }
        if (realm == null) {
            return false;
        }

        if (nonInteractive) {
            library.SecKeychainSetUserInteractionAllowed(false);
        }

        byte[] rawPassword = null;
        try {
            byte[] rawRealm = realm.getBytes("UTF-8");
            byte[] rawUserName = userName == null ? null : userName.getBytes("UTF-8");
            int rawUserNameLength = userName == null ? 0 : rawUserName.length;
            rawPassword = SVNEncodingUtil.getBytes(password, "UTF-8");

            PointerByReference itemHolder = new PointerByReference();
            int status = library.SecKeychainFindGenericPassword(null, rawRealm.length, rawRealm,
                    rawUserNameLength, rawUserName, null, null, itemHolder);

            if (status == ERR_SEC_ITEM_NOT_FOUND) {
                status = library.SecKeychainAddGenericPassword(null, rawRealm.length, rawRealm,
                        rawUserNameLength, rawUserName, rawPassword.length, rawPassword, null);
            } else {
                Pointer item = itemHolder.getValue();
                try {
                    status = library.SecKeychainItemModifyAttributesAndData(item, null, rawPassword.length, rawPassword);
                } finally {
                    release(item);
                }
            }

            return status == 0;
        } catch (UnsupportedEncodingException e) {
            SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e);
            SVNErrorManager.error(error, e, SVNLogType.DEFAULT);
        } finally {
            if (nonInteractive) {
                library.SecKeychainSetUserInteractionAllowed(true);
            }
            SVNEncodingUtil.clearArray(rawPassword);
        }
        
        return false;
    }

    public static synchronized char[] getPassword(String realm, String userName, boolean nonInteractive) throws SVNException {
        ISVNMacOsSecurityLibrary library = JNALibraryLoader.getMacOsSecurityLibrary();
        if (library == null) {
            return null;
        }

        if (realm == null) {
            return null;
        }

        if (nonInteractive) {
            library.SecKeychainSetUserInteractionAllowed(false);
        }

        try {
            byte[] rawRealm = realm.getBytes("UTF-8");
            byte[] rawUserName = userName == null ? null : userName.getBytes("UTF-8");
            int rawUserNameLength = userName == null ? 0 : rawUserName.length;
            IntByReference passwordLengthHolder = new IntByReference();
            PointerByReference passwordHolder = new PointerByReference();

            int status = library.SecKeychainFindGenericPassword(null, rawRealm.length, rawRealm, rawUserNameLength, rawUserName,
                    passwordLengthHolder, passwordHolder, null);

            if (status != 0) {
                return null;
            }

            Pointer passwordPointer = passwordHolder.getValue();
            if (passwordPointer == null) {
                return null;
            }

            int passwordLength = passwordLengthHolder.getValue();
            byte[] rawPassword = passwordPointer.getByteArray(0, passwordLength);

            char[] password;
            try {
                password = SVNEncodingUtil.getChars(rawPassword, "UTF-8");
            } finally {
                library.SecKeychainItemFreeContent(null, passwordPointer);
                SVNEncodingUtil.clearArray(rawPassword);
            }

            return password;
        } catch (UnsupportedEncodingException e) {
            SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e);
            SVNErrorManager.error(error, e, SVNLogType.DEFAULT);
            return null;
        } finally {
            if (nonInteractive) {
                library.SecKeychainSetUserInteractionAllowed(true);
            }
        }
    }

    private static void release(Pointer pointer) {
        if (pointer != null) {
            ISVNMacOsCFLibrary library = JNALibraryLoader.getMacOsCFLibrary();
            if (library != null) {
                library.CFRelease(pointer);
            }
        }
    }
}
