/*
 * Copyright (C) 2006 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.internal.telephony.gsm;

import android.database.Cursor;
import android.os.Message;
import android.os.SystemProperties;
import android.os.SystemService;
import android.util.Log;

import com.android.internal.telephony.DataLink;
import com.android.internal.telephony.DataConnectionTracker.State;
import com.android.internal.telephony.PhoneBase;
import com.android.internal.util.ArrayUtils;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * Represents a PPP link.
 *
 * Ideally this would be managed by the RIL implementation, but
 * we currently have implementations where this is not the case.
 *
 * {@hide}
 */
final class PppLink extends DataLink {
    private static final String LOG_TAG = "GSM";

    static final String PATH_PPP_OPERSTATE = "/sys/class/net/ppp0/operstate";
    static final String SERVICE_PPPD_GPRS = "pppd_gprs";
    static final String PROPERTY_PPPD_EXIT_CODE = "net.gprs.ppp-exit";
    static final int POLL_SYSFS_MILLIS = 5 * 1000;
    static final int EVENT_POLL_DATA_CONNECTION = 2;
    static final int EVENT_PPP_OPERSTATE_CHANGED = 8;
    static final int EVENT_PPP_PIDFILE_CHANGED = 9;

    static final byte[] UP_ASCII_STRING = new byte[] {
        'u' & 0xff,
        'p' & 0xff,
    };
    static final byte[] DOWN_ASCII_STRING = new byte[] {
        'd' & 0xff,
        'o' & 0xff,
        'w' & 0xff,
        'n' & 0xff,
    };
    static final byte[] UNKNOWN_ASCII_STRING = new byte[] {
        'u' & 0xff,
        'n' & 0xff,
        'k' & 0xff,
        'n' & 0xff,
        'o' & 0xff,
        'w' & 0xff,
        'n' & 0xff,
    };
    private final byte[] mCheckPPPBuffer = new byte[32];

    private PhoneBase phone;

    int lastPppdExitCode = EXIT_OK;


    PppLink(GsmDataConnectionTracker dc, GSMPhone p) {
        super(dc);
        this.phone = p;
    }

    public void connect() {
        // Clear any previous exit code
        SystemProperties.set(PROPERTY_PPPD_EXIT_CODE, "");
        SystemService.start(SERVICE_PPPD_GPRS);
        removeMessages(EVENT_POLL_DATA_CONNECTION);
        Message poll = obtainMessage();
        poll.what = EVENT_POLL_DATA_CONNECTION;
        sendMessageDelayed(poll, POLL_SYSFS_MILLIS);
    }

    public void disconnect() {
        SystemService.stop(SERVICE_PPPD_GPRS);
    }

    public int getLastLinkExitCode() {
        return lastPppdExitCode;
    }

    public void setPasswordInfo(Cursor cursor) {
        StringBuilder builder = new StringBuilder();
        FileOutputStream output = null;

        try {
            output = new FileOutputStream("/etc/ppp/pap-secrets");
            if (cursor.moveToFirst()) {
                do {
                    builder.append(cursor.getString(cursor.getColumnIndex("user")));
                    builder.append(" ");
                    builder.append(cursor.getString(cursor.getColumnIndex("server")));
                    builder.append(" ");
                    builder.append(cursor.getString(cursor.getColumnIndex("password")));
                    builder.append("\n");
                } while (cursor.moveToNext());
            }

            output.write(builder.toString().getBytes());
        } catch (java.io.IOException e) {
            Log.e(LOG_TAG, "Could not create '/etc/ppp/pap-secrets'", e);
        } finally {
            try {
                if (output != null) output.close();
            } catch (java.io.IOException e) {
                Log.e(LOG_TAG, "Error closing '/etc/ppp/pap-secrets'", e);
            }
        }
    }

    public void handleMessage (Message msg) {

        switch (msg.what) {

            case EVENT_POLL_DATA_CONNECTION:
                checkPPP();

                // keep polling in case interface goes down
                if (dataConnection.getState() != State.IDLE) {
                    Message poll = obtainMessage();
                    poll.what = EVENT_POLL_DATA_CONNECTION;
                    sendMessageDelayed(poll, POLL_SYSFS_MILLIS);
                }
                break;
        }
    }

    private void checkPPP() {
        boolean connecting = (dataConnection.getState() == State.CONNECTING);

        try {
            RandomAccessFile file = new RandomAccessFile(PATH_PPP_OPERSTATE, "r");
            file.read(mCheckPPPBuffer);
            file.close();

            // Unfortunately, we're currently seeing operstate
            // "unknown" where one might otherwise expect "up"
            if (ArrayUtils.equals(mCheckPPPBuffer, UP_ASCII_STRING, UP_ASCII_STRING.length)
                    || ArrayUtils.equals(mCheckPPPBuffer, UNKNOWN_ASCII_STRING,
                    UNKNOWN_ASCII_STRING.length)
                    && dataConnection.getState() == State.CONNECTING) {

                Log.i(LOG_TAG,
                "found ppp interface. Notifying GPRS connected");

                if (mLinkChangeRegistrant != null) {
                    mLinkChangeRegistrant.notifyResult(LinkState.LINK_UP);
                }

                connecting = false;
            } else if (dataConnection.getState() == State.CONNECTED
                    && ArrayUtils.equals(mCheckPPPBuffer, DOWN_ASCII_STRING,
                    DOWN_ASCII_STRING.length)) {

                Log.i(LOG_TAG,
                "ppp interface went down. Reconnecting...");

                if (mLinkChangeRegistrant != null) {
                    mLinkChangeRegistrant.notifyResult(LinkState.LINK_DOWN);
                }
            }
        } catch (IOException ex) {
            if (! (ex instanceof FileNotFoundException)) {
                Log.i(LOG_TAG, "Poll ppp0 ex " + ex.toString());
            }

            if (dataConnection.getState() == State.CONNECTED &&
                    mLinkChangeRegistrant != null) {
                mLinkChangeRegistrant.notifyResult(LinkState.LINK_DOWN);
            }
        }

        // CONNECTING means pppd has started but negotiation is not complete
        // If we're still CONNECTING here, check to see if pppd has
        // already exited
        if (connecting) {
            String exitCode;

            exitCode = SystemProperties.get(PROPERTY_PPPD_EXIT_CODE, "");

            if (!exitCode.equals("")) {
                // pppd has exited. Let's figure out why
                lastPppdExitCode = Integer.parseInt(exitCode);

                Log.d(LOG_TAG,"pppd exited with " + exitCode);

                if (mLinkChangeRegistrant != null) {
                    mLinkChangeRegistrant.notifyResult(LinkState.LINK_EXITED);
                }
            }
        }

    }

    protected void log(String s) {
        Log.d(LOG_TAG, "[PppLink] " + s);
    }
}
