/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * 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.
 */
package android.os;

import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.util.Locale;
import static libcore.io.OsConstants.*;

class CommonTimeUtils {
    /**
     * Successful operation.
     */
    public static final int SUCCESS = 0;
    /**
     * Unspecified error.
     */
    public static final int ERROR = -1;
    /**
     * Operation failed due to bad parameter value.
     */
    public static final int ERROR_BAD_VALUE = -4;
    /**
     * Operation failed due to dead remote object.
     */
    public static final int ERROR_DEAD_OBJECT = -7;

    public CommonTimeUtils(IBinder remote, String interfaceDesc) {
        mRemote = remote;
        mInterfaceDesc = interfaceDesc;
    }

    public int transactGetInt(int method_code, int error_ret_val)
    throws RemoteException {
        android.os.Parcel data  = android.os.Parcel.obtain();
        android.os.Parcel reply = android.os.Parcel.obtain();
        int ret_val;

        try {
            int res;
            data.writeInterfaceToken(mInterfaceDesc);
            mRemote.transact(method_code, data, reply, 0);

            res = reply.readInt();
            ret_val = (0 == res) ? reply.readInt() : error_ret_val;
        }
        finally {
            reply.recycle();
            data.recycle();
        }

        return ret_val;
    }

    public int transactSetInt(int method_code, int val) {
        android.os.Parcel data  = android.os.Parcel.obtain();
        android.os.Parcel reply = android.os.Parcel.obtain();

        try {
            data.writeInterfaceToken(mInterfaceDesc);
            data.writeInt(val);
            mRemote.transact(method_code, data, reply, 0);

            return reply.readInt();
        }
        catch (RemoteException e) {
            return ERROR_DEAD_OBJECT;
        }
        finally {
            reply.recycle();
            data.recycle();
        }
    }

    public long transactGetLong(int method_code, long error_ret_val)
    throws RemoteException {
        android.os.Parcel data  = android.os.Parcel.obtain();
        android.os.Parcel reply = android.os.Parcel.obtain();
        long ret_val;

        try {
            int res;
            data.writeInterfaceToken(mInterfaceDesc);
            mRemote.transact(method_code, data, reply, 0);

            res = reply.readInt();
            ret_val = (0 == res) ? reply.readLong() : error_ret_val;
        }
        finally {
            reply.recycle();
            data.recycle();
        }

        return ret_val;
    }

    public int transactSetLong(int method_code, long val) {
        android.os.Parcel data  = android.os.Parcel.obtain();
        android.os.Parcel reply = android.os.Parcel.obtain();

        try {
            data.writeInterfaceToken(mInterfaceDesc);
            data.writeLong(val);
            mRemote.transact(method_code, data, reply, 0);

            return reply.readInt();
        }
        catch (RemoteException e) {
            return ERROR_DEAD_OBJECT;
        }
        finally {
            reply.recycle();
            data.recycle();
        }
    }

    public String transactGetString(int method_code, String error_ret_val)
    throws RemoteException {
        android.os.Parcel data  = android.os.Parcel.obtain();
        android.os.Parcel reply = android.os.Parcel.obtain();
        String ret_val;

        try {
            int res;
            data.writeInterfaceToken(mInterfaceDesc);
            mRemote.transact(method_code, data, reply, 0);

            res = reply.readInt();
            ret_val = (0 == res) ? reply.readString() : error_ret_val;
        }
        finally {
            reply.recycle();
            data.recycle();
        }

        return ret_val;
    }

    public int transactSetString(int method_code, String val) {
        android.os.Parcel data  = android.os.Parcel.obtain();
        android.os.Parcel reply = android.os.Parcel.obtain();

        try {
            data.writeInterfaceToken(mInterfaceDesc);
            data.writeString(val);
            mRemote.transact(method_code, data, reply, 0);

            return reply.readInt();
        }
        catch (RemoteException e) {
            return ERROR_DEAD_OBJECT;
        }
        finally {
            reply.recycle();
            data.recycle();
        }
    }

    public InetSocketAddress transactGetSockaddr(int method_code)
    throws RemoteException {
        android.os.Parcel data  = android.os.Parcel.obtain();
        android.os.Parcel reply = android.os.Parcel.obtain();
        InetSocketAddress ret_val = null;

        try {
            int res;
            data.writeInterfaceToken(mInterfaceDesc);
            mRemote.transact(method_code, data, reply, 0);

            res = reply.readInt();
            if (0 == res) {
                int type;
                int port = 0;
                String addrStr = null;

                type = reply.readInt();

                if (AF_INET == type) {
                    int addr = reply.readInt();
                    port = reply.readInt();
                    addrStr = String.format(Locale.US, "%d.%d.%d.%d",
                                                       (addr >> 24) & 0xFF,
                                                       (addr >> 16) & 0xFF,
                                                       (addr >>  8) & 0xFF,
                                                        addr        & 0xFF);
                } else if (AF_INET6 == type) {
                    int addr1 = reply.readInt();
                    int addr2 = reply.readInt();
                    int addr3 = reply.readInt();
                    int addr4 = reply.readInt();

                    port = reply.readInt();

                    int flowinfo = reply.readInt();
                    int scope_id = reply.readInt();

                    addrStr = String.format(Locale.US, "[%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X]",
                                                       (addr1 >> 16) & 0xFFFF, addr1 & 0xFFFF,
                                                       (addr2 >> 16) & 0xFFFF, addr2 & 0xFFFF,
                                                       (addr3 >> 16) & 0xFFFF, addr3 & 0xFFFF,
                                                       (addr4 >> 16) & 0xFFFF, addr4 & 0xFFFF);
                }

                if (null != addrStr) {
                    ret_val = new InetSocketAddress(addrStr, port);
                }
            }
        }
        finally {
            reply.recycle();
            data.recycle();
        }

        return ret_val;
    }

    public int transactSetSockaddr(int method_code, InetSocketAddress addr) {
        android.os.Parcel data  = android.os.Parcel.obtain();
        android.os.Parcel reply = android.os.Parcel.obtain();
        int ret_val = ERROR;

        try {
            data.writeInterfaceToken(mInterfaceDesc);

            if (null == addr) {
                data.writeInt(0);
            } else {
                data.writeInt(1);
                final InetAddress a = addr.getAddress();
                final byte[]      b = a.getAddress();
                final int         p = addr.getPort();

                if (a instanceof Inet4Address) {
                    int v4addr = (((int)b[0] & 0xFF) << 24) |
                                 (((int)b[1] & 0xFF) << 16) |
                                 (((int)b[2] & 0xFF) << 8) |
                                  ((int)b[3] & 0xFF);

                    data.writeInt(AF_INET);
                    data.writeInt(v4addr);
                    data.writeInt(p);
                } else
                if (a instanceof Inet6Address) {
                    int i;
                    Inet6Address v6 = (Inet6Address)a;
                    data.writeInt(AF_INET6);
                    for (i = 0; i < 4; ++i) {
                        int aword = (((int)b[(i*4) + 0] & 0xFF) << 24) |
                                    (((int)b[(i*4) + 1] & 0xFF) << 16) |
                                    (((int)b[(i*4) + 2] & 0xFF) << 8) |
                                     ((int)b[(i*4) + 3] & 0xFF);
                        data.writeInt(aword);
                    }
                    data.writeInt(p);
                    data.writeInt(0);   // flow info
                    data.writeInt(v6.getScopeId());
                } else {
                    return ERROR_BAD_VALUE;
                }
            }

            mRemote.transact(method_code, data, reply, 0);
            ret_val = reply.readInt();
        }
        catch (RemoteException e) {
            ret_val = ERROR_DEAD_OBJECT;
        }
        finally {
            reply.recycle();
            data.recycle();
        }

        return ret_val;
    }

    private IBinder mRemote;
    private String mInterfaceDesc;
};
