/*
 * Copyright (C) 2014 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 com.android.mms.service;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SqliteWrapper;
import android.net.NetworkUtils;
import android.net.Uri;
import android.provider.Telephony;
import android.text.TextUtils;

import com.android.internal.telephony.PhoneConstants;
import com.android.mms.service.exception.ApnException;

import java.net.URI;
import java.net.URISyntaxException;

/**
 * APN settings used for MMS transactions
 */
public class ApnSettings {
    // MMSC URL
    private final String mServiceCenter;
    // MMSC proxy address
    private final String mProxyAddress;
    // MMSC proxy port
    private final int mProxyPort;
    // Debug text for this APN: a concatenation of interesting columns of this APN
    private final String mDebugText;

    private static final String[] APN_PROJECTION = {
            Telephony.Carriers.TYPE,
            Telephony.Carriers.MMSC,
            Telephony.Carriers.MMSPROXY,
            Telephony.Carriers.MMSPORT,
            Telephony.Carriers.NAME,
            Telephony.Carriers.APN,
            Telephony.Carriers.BEARER_BITMASK,
            Telephony.Carriers.PROTOCOL,
            Telephony.Carriers.ROAMING_PROTOCOL,
            Telephony.Carriers.AUTH_TYPE,
            Telephony.Carriers.MVNO_TYPE,
            Telephony.Carriers.MVNO_MATCH_DATA,
            Telephony.Carriers.PROXY,
            Telephony.Carriers.PORT,
            Telephony.Carriers.SERVER,
            Telephony.Carriers.USER,
            Telephony.Carriers.PASSWORD,
    };
    private static final int COLUMN_TYPE         = 0;
    private static final int COLUMN_MMSC         = 1;
    private static final int COLUMN_MMSPROXY     = 2;
    private static final int COLUMN_MMSPORT      = 3;
    private static final int COLUMN_NAME         = 4;
    private static final int COLUMN_APN          = 5;
    private static final int COLUMN_BEARER       = 6;
    private static final int COLUMN_PROTOCOL     = 7;
    private static final int COLUMN_ROAMING_PROTOCOL = 8;
    private static final int COLUMN_AUTH_TYPE    = 9;
    private static final int COLUMN_MVNO_TYPE    = 10;
    private static final int COLUMN_MVNO_MATCH_DATA = 11;
    private static final int COLUMN_PROXY        = 12;
    private static final int COLUMN_PORT         = 13;
    private static final int COLUMN_SERVER       = 14;
    private static final int COLUMN_USER         = 15;
    private static final int COLUMN_PASSWORD     = 16;


    /**
     * Load APN settings from system
     *  @param context
     * @param apnName the optional APN name to match
     * @param requestId the request ID for logging
     */
    public static ApnSettings load(Context context, String apnName, int subId, String requestId)
            throws ApnException {
        LogUtil.i(requestId, "Loading APN using name " + apnName);
        // TODO: CURRENT semantics is currently broken in telephony. Revive this when it is fixed.
        //String selection = Telephony.Carriers.CURRENT + " IS NOT NULL";
        String selection = null;
        String[] selectionArgs = null;
        apnName = apnName != null ? apnName.trim() : null;
        if (!TextUtils.isEmpty(apnName)) {
            //selection += " AND " + Telephony.Carriers.APN + "=?";
            selection = Telephony.Carriers.APN + "=?";
            selectionArgs = new String[]{ apnName };
        }
        Cursor cursor = null;
        try {
            cursor = SqliteWrapper.query(
                    context,
                    context.getContentResolver(),
                    Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "/subId/" + subId),
                    APN_PROJECTION,
                    selection,
                    selectionArgs,
                    null/*sortOrder*/);
            if (cursor != null) {
                String mmscUrl = null;
                String proxyAddress = null;
                // Default proxy port to 80
                int proxyPort = 80;
                while (cursor.moveToNext()) {
                    // Read values from APN settings
                    if (isValidApnType(
                            cursor.getString(COLUMN_TYPE), PhoneConstants.APN_TYPE_MMS)) {
                        mmscUrl = trimWithNullCheck(cursor.getString(COLUMN_MMSC));
                        if (TextUtils.isEmpty(mmscUrl)) {
                            continue;
                        }
                        mmscUrl = NetworkUtils.trimV4AddrZeros(mmscUrl);
                        try {
                            new URI(mmscUrl);
                        } catch (URISyntaxException e) {
                            throw new ApnException("Invalid MMSC url " + mmscUrl);
                        }
                        proxyAddress = trimWithNullCheck(cursor.getString(COLUMN_MMSPROXY));
                        if (!TextUtils.isEmpty(proxyAddress)) {
                            proxyAddress = NetworkUtils.trimV4AddrZeros(proxyAddress);
                            final String portString =
                                    trimWithNullCheck(cursor.getString(COLUMN_MMSPORT));
                            if (!TextUtils.isEmpty(portString)) {
                                try {
                                    proxyPort = Integer.parseInt(portString);
                                } catch (NumberFormatException e) {
                                    LogUtil.e(requestId, "Invalid port " + portString + ", use 80");
                                }
                            }
                        }
                        return new ApnSettings(
                                mmscUrl, proxyAddress, proxyPort, getDebugText(cursor));
                    }
                }

            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        throw new ApnException("Can not find valid APN");
    }

    private static String getDebugText(Cursor cursor) {
        final StringBuilder sb = new StringBuilder();
        sb.append("APN [");
        for (int i = 0; i < cursor.getColumnCount(); i++) {
            final String name = cursor.getColumnName(i);
            final String value = cursor.getString(i);
            if (TextUtils.isEmpty(value)) {
                continue;
            }
            if (i > 0) {
                sb.append(' ');
            }
            sb.append(name).append('=').append(value);
        }
        sb.append("]");
        return sb.toString();
    }

    private static String trimWithNullCheck(String value) {
        return value != null ? value.trim() : null;
    }

    public ApnSettings(String mmscUrl, String proxyAddr, int proxyPort, String debugText) {
        mServiceCenter = mmscUrl;
        mProxyAddress = proxyAddr;
        mProxyPort = proxyPort;
        mDebugText = debugText;
   }

    public String getMmscUrl() {
        return mServiceCenter;
    }

    public String getProxyAddress() {
        return mProxyAddress;
    }

    public int getProxyPort() {
        return mProxyPort;
    }

    public boolean isProxySet() {
        return !TextUtils.isEmpty(mProxyAddress);
    }

    private static boolean isValidApnType(String types, String requestType) {
        // If APN type is unspecified, assume APN_TYPE_ALL.
        if (TextUtils.isEmpty(types)) {
            return true;
        }
        for (String type : types.split(",")) {
            type = type.trim();
            if (type.equals(requestType) || type.equals(PhoneConstants.APN_TYPE_ALL)) {
                return true;
            }
        }
        return false;
    }

    public String toString() {
        return mDebugText;
    }
}
