/*
 * Copyright (C) 2010 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.cneeds.settings.bean;

import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;

import com.example.imlinkclient.utils.InvokeUtil;


public class AccessPoint implements Parcelable, Comparable<AccessPoint> {
    static final String TAG = "Settings.AccessPoint";

    /** {@hide} */
    public static final int INVALID_NETWORK_ID = -1;
    
    public static final int SECURITY_NONE = 0;
    public static final int SECURITY_WEP = 1;
    public static final int SECURITY_PSK = 2;
    public static final int SECURITY_EAP = 3;

    public enum PskType {
        UNKNOWN,
        WPA,
        WPA2,
        WPA_WPA2
    }

    private PskType pskType = PskType.UNKNOWN;

    private String ssid;
    private String bssid;
    private int security;
    private int networkId = -1;
    boolean wpsAvailable = false;
    private int mRssi = Integer.MAX_VALUE;
    private long mSeen = 0;

    private ScanResult mScanResult;
    private WifiConfiguration mConfig;
    private WifiInfo mInfo;
    private NetworkInfo mNetworkInfo;

    public AccessPoint(WifiConfiguration config) {
        loadConfig(config);
    }

    public AccessPoint(ScanResult result) {
        loadResult(result);
    }

    private void loadConfig(WifiConfiguration config) {
        synchronized (AccessPoint.class) {
            ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
            bssid = config.BSSID;
            security = getSecurity(config);
            networkId = config.networkId;
            mConfig = config;
        }
    }

    private void loadResult(ScanResult result) {
        synchronized (AccessPoint.class) {
            ssid = result.SSID;
            bssid = result.BSSID;
            security = getSecurity(result);
            wpsAvailable = security != SECURITY_EAP && result.capabilities.contains("WPS");
            if (security == SECURITY_PSK) {
                pskType = getPskType(result);
            }
            mRssi = result.level;
            mScanResult = result;
            Long seen = InvokeUtil.getDeclaredField(ScanResult.class, "seen", result);
            if (seen > mSeen) {
                mSeen = seen;
            }
        }
    }

    @Override
    public int compareTo(AccessPoint other) {
        synchronized (AccessPoint.class) {
            // Active one goes first.
            if (isActive() && !other.isActive()) return -1;
            if (!isActive() && other.isActive()) return 1;

            // Reachable one goes before unreachable one.
            if (mRssi != Integer.MAX_VALUE && other.mRssi == Integer.MAX_VALUE) return -1;
            if (mRssi == Integer.MAX_VALUE && other.mRssi != Integer.MAX_VALUE) return 1;

            // Configured one goes before unconfigured one.
            if (networkId != INVALID_NETWORK_ID
                    && other.networkId == INVALID_NETWORK_ID) return -1;
            if (networkId == INVALID_NETWORK_ID
                    && other.networkId != INVALID_NETWORK_ID) return 1;

            // Sort by signal strength.
            int difference = WifiManager.compareSignalLevel(other.mRssi, mRssi);
            if (difference != 0) {
                return difference;
            }
            // Sort by ssid.
            return ssid.compareToIgnoreCase(other.ssid);
        }
    }

    public boolean update(ScanResult result) {
        synchronized (AccessPoint.class) {
            Long seen = InvokeUtil.getDeclaredField(ScanResult.class, "seen", result);
            if (seen > mSeen) {
                mSeen = seen;
            }
            if (ssid.equals(result.SSID) && security == getSecurity(result)) {
                if (WifiManager.compareSignalLevel(result.level, mRssi) > 0) {
                    mRssi = result.level;
                }
                // This flag only comes from scans, is not easily saved in config
                if (security == SECURITY_PSK) {
                    pskType = getPskType(result);
                }
                mScanResult = result;
                return true;
            }
            return false;
        }
    }

    public void update(WifiInfo info, NetworkInfo networkInfo) {
        synchronized (AccessPoint.class) {
            if (info != null && ssid.equals(removeDoubleQuotes(info.getSSID()))) {
                mRssi = info.getRssi();
                mInfo = info;
                mNetworkInfo = networkInfo;
            } else {
                mInfo = null;
                mNetworkInfo = null;
            }
        }
    }

    public boolean isActive() {
        return mNetworkInfo != null;
//        return mNetworkInfo != null &&
//                (networkId != INVALID_NETWORK_ID || mNetworkInfo.getState() != State.DISCONNECTED);
    }

    public void generateOpenNetworkConfig() {
        synchronized (AccessPoint.class) {
            if (security != SECURITY_NONE) {
                throw new IllegalStateException();
            }
            if (mConfig != null) {
                return;
            }
            mConfig = new WifiConfiguration();
            mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
            mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
        }
    }

    private static String removeDoubleQuotes(String string) {
        int length = string.length();
        if ((length > 1) && (string.charAt(0) == '"')
                && (string.charAt(length - 1) == '"')) {
            return string.substring(1, length - 1);
        }
        return string;
    }

    public static String convertToQuotedString(String string) {
        return "\"" + string + "\"";
    }

    private static int getSecurity(WifiConfiguration config) {
        if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
            return SECURITY_PSK;
        }
        if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
                config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
            return SECURITY_EAP;
        }
        return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE;
    }

    private static int getSecurity(ScanResult result) {
        if (result.capabilities.contains("WEP")) {
            return SECURITY_WEP;
        } else if (result.capabilities.contains("PSK")) {
            return SECURITY_PSK;
        } else if (result.capabilities.contains("EAP")) {
            return SECURITY_EAP;
        }
        return SECURITY_NONE;
    }

    private static PskType getPskType(ScanResult result) {
        boolean wpa = result.capabilities.contains("WPA-PSK");
        boolean wpa2 = result.capabilities.contains("WPA2-PSK");
        if (wpa2 && wpa) {
            return PskType.WPA_WPA2;
        } else if (wpa2) {
            return PskType.WPA2;
        } else if (wpa) {
            return PskType.WPA;
        } else {
            Log.w(TAG, "Received abnormal flag string: " + result.capabilities);
            return PskType.UNKNOWN;
        }
    }


    public int getLevel() {
        if (mRssi == Integer.MAX_VALUE) {
            return -1;
        }
        return WifiManager.calculateSignalLevel(mRssi, 4);
    }

    public WifiConfiguration getConfig() {
        return mConfig;
    }

    public WifiInfo getInfo() {
        return mInfo;
    }

    public NetworkInfo getNetworkInfo() {
        return mNetworkInfo;
    }

    public DetailedState getState() {
        return mNetworkInfo != null ? mNetworkInfo.getDetailedState() : null;
    }

    public String getBssid() {
        return bssid;
    }

    public String getSsid() {
        return ssid;
    }

    public int getSecurity() {
        return security;
    }

    public int getNetworkId() {
        return networkId;
    }

    public boolean isWpsAvailable() {
        return wpsAvailable;
    }

    public PskType getPskType() {
        return pskType;
    }

    public ScanResult getmScanResult() {
        return mScanResult;
    }

    public int getmRssi() {
        return mRssi;
    }

    public long getmSeen() {
        return mSeen;
    }

    public WifiInfo getmInfo() {
        return mInfo;
    }

    public NetworkInfo getmNetworkInfo() {
        return mNetworkInfo;
    }

    public void setNetworkInfo(NetworkInfo mNetworkInfo) {
        this.mNetworkInfo = mNetworkInfo;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.pskType == null ? -1 : this.pskType.ordinal());
        dest.writeString(this.ssid);
        dest.writeString(this.bssid);
        dest.writeInt(this.security);
        dest.writeInt(this.networkId);
        dest.writeByte(this.wpsAvailable ? (byte) 1 : (byte) 0);
        dest.writeInt(this.mRssi);
        dest.writeLong(this.mSeen);
        dest.writeParcelable(this.mScanResult, flags);
        dest.writeParcelable(this.mConfig, flags);
        dest.writeParcelable(this.mInfo, flags);
        dest.writeParcelable(this.mNetworkInfo, flags);
    }

    protected AccessPoint(Parcel in) {
        int tmpPskType = in.readInt();
        this.pskType = tmpPskType == -1 ? null : PskType.values()[tmpPskType];
        this.ssid = in.readString();
        this.bssid = in.readString();
        this.security = in.readInt();
        this.networkId = in.readInt();
        this.wpsAvailable = in.readByte() != 0;
        this.mRssi = in.readInt();
        this.mSeen = in.readLong();
        this.mScanResult = in.readParcelable(ScanResult.class.getClassLoader());
        this.mConfig = in.readParcelable(WifiConfiguration.class.getClassLoader());
        this.mInfo = in.readParcelable(WifiInfo.class.getClassLoader());
        this.mNetworkInfo = in.readParcelable(NetworkInfo.class.getClassLoader());
    }

    public static final Creator<AccessPoint> CREATOR = new Creator<AccessPoint>() {
        @Override
        public AccessPoint createFromParcel(Parcel source) {
            return new AccessPoint(source);
        }

        @Override
        public AccessPoint[] newArray(int size) {
            return new AccessPoint[size];
        }
    };
}
