package org.maachang.util.net;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.maachang.util.Os;
import org.maachang.util.RunProcess;

/**
 * ネットワークアダプタ情報取得基本. <BR>
 * <BR>
 * 基本となるネットワークアダプタ情報取得処理をサポートします.
 * 
 * @version 2008/10/28
 * @author masahito suzuki
 * @since MaachangBase 1.08
 */
abstract class AbstractNetAdapter implements NetAdapter {

    /**
     * 内部アドレス媒体名.
     */
    protected static final String DEFAULT_ETHER_NAME = "lo";

    /**
     * 内部サブネットマスク.
     */
    protected static final String LO_HOST_SUB = "255.0.0.0";

    /**
     * LocalIP.
     */
    protected static final String LOCAL_IP = "127.0.0.1";

    /**
     * アドレス格納情報.
     */
    protected Map<String, NetAdapterElement> table = null;

    /**
     * 初期化処理.
     */
    protected void init() {
        table = Collections
                .synchronizedMap(new HashMap<String, NetAdapterElement>());
    }

    /**
     * 情報クリア.
     */
    public void clear() {
        table.clear();
    }

    /**
     * ネットワーク情報取得処理.
     * 
     * @exception Exception
     *                例外.
     */
    public abstract void load() throws Exception;

    /**
     * 媒体情報の追加.
     * 
     * @para name 対象の媒体名を設定します.
     * @param element
     *            対象の要素を設定します.
     */
    protected final void put(String name, NetAdapterElement element) {
        if (name == null || element == null
                || (name = name.trim()).length() <= 0) {
            throw new IllegalArgumentException("引数は不正です");
        }
        table.put(name, element);
    }

    /**
     * 指定媒体名を取得.
     * 
     * @param name
     *            対象の媒体名(LANの場合[eth0]など.)を設定します.
     * @return NetAdapterElement 要素が返されます.
     */
    public NetAdapterElement get(String name) {
        if (name == null || (name = name.trim()).length() <= 0) {
            return null;
        }
        return table.get(name);
    }

    /**
     * 媒体名群の取得.
     * 
     * @return Iterator Iteratorが返されます.
     */
    public Iterator iterator() {
        return table.keySet().iterator();
    }

    /**
     * 媒体数を取得.
     * 
     * @return int 媒体数が返されます.
     */
    public int size() {
        return table.size();
    }
    
    /**
     * 文字列変換.
     * @return String 文字列に変換されます.
     */
    public String toString() {
        if( table == null ) {
            return "null" ;
        }
        int cnt = 0 ;
        StringBuilder buf = new StringBuilder() ;
        Iterator it = table.keySet().iterator() ;
        while( it.hasNext() ) {
            if( cnt != 0 ) {
                buf.append( "," ) ;
            }
            String key = ( String )it.next() ;
            NetAdapterElement em = table.get( key ) ;
            buf.append( "ethName:" ).append( key ).append( " {" ).
                append( em.toString() ).append( " } " ) ;
        }
        return buf.toString() ;
    }

    /**
     * IPアドレスを設定.
     */
    protected void setIpAddress(String name, String value) {
        if (name == null || (name = name.trim()).length() <= 0 || value == null
                || (value = value.trim()).length() <= 0) {
            throw new IllegalArgumentException("引数は不正です");
        }
        NetAdapterElement em = table.get(name);
        if (em == null) {
            em = new NetAdapterElement();
            table.put(name, em);
        }
        em.setAddr(value);
    }

    /**
     * サブネットマスクを設定.
     */
    protected void setSubnetMask(String name, String value) {
        if (name == null || (name = name.trim()).length() <= 0 || value == null
                || (value = value.trim()).length() <= 0) {
            throw new IllegalArgumentException("引数は不正です");
        }
        NetAdapterElement em = table.get(name);
        if (em == null) {
            em = new NetAdapterElement();
            table.put(name, em);
        }
        em.setNetMask(value);
    }

    /**
     * MACアドレスを設定.
     */
    protected void setMacAddress(String name, String value) {
        if (name == null || (name = name.trim()).length() <= 0 || value == null
                || (value = value.trim()).length() <= 0) {
            throw new IllegalArgumentException("引数は不正です");
        }
        NetAdapterElement em = table.get(name);
        if (em == null) {
            em = new NetAdapterElement();
            table.put(name, em);
        }
        em.setMacAddr(value);
    }

    /**
     * コマンド実行.
     */
    protected static List<String> runExec(String exec) throws Exception {
        if (exec == null || (exec = exec.trim()).length() <= 0) {
            throw new IllegalArgumentException("引数は不正です");
        }
        String[] cmd;
        switch (Os.getInstance().getOS()) {
        case Os.OS_WINNT:
            cmd = new String[] { "cmd", "/c", exec };
            break;
        case Os.OS_UNIX:
        case Os.OS_MAC_OS_X:
            cmd = new String[] { "/bin/sh", "-c", exec };
            break;
        default:
            throw new IOException("実行中のOSでは利用できません");
        }
        ArrayList<String> ret = new ArrayList<String>();
        RunProcess runProc = new RunProcess( cmd ) ;
        try {
            BufferedReader reader = new BufferedReader( new InputStreamReader( runProc.execution() ) ) ;
            for (;;) {
                String s = reader.readLine();
                if (s == null) {
                    break;
                }
                ret.add(s);
            }
            runProc.waitFor() ;
        } finally {
            runProc.destroy() ;
        }
        return ret;
    }

    /**
     * 変換された16進データを対象のバイナリに変換.
     * 
     * @param code
     *            16進数で構成された文字列を指定します.
     * @return byte[] 変換されたバイナリが返されます.
     * @exception Exception
     *                例外.
     */
    protected static final byte[] convertString16ToBinary(String code)
            throws Exception {
        int i, j;
        int len;
        char[] cnv = null;
        byte[] ret = null;
        int[] tmp = null;
        try {
            len = code.length();
            if (len % 2 == 1) {
                throw new IllegalArgumentException("指定文字長は、奇数では対応されていません");
            }
            code.toUpperCase();
            len /= 2;
            ret = new byte[len];
            cnv = new char[2];
            tmp = new int[2];
            for (i = 0, j = 0; i < len; i++, j += 2) {
                cnv[0] = code.charAt(j);
                cnv[1] = code.charAt(j + 1);
                tmp[0] = ((cnv[0] >= '0' && cnv[0] <= '9') ? cnv[0] - '0'
                        : ((cnv[0] >= 'A' && cnv[0] <= 'F') ? cnv[0] - 'A' + 10
                                : (((cnv[0] >= 'a' && cnv[0] <= 'f') ? cnv[0] - 'a' + 10
                                        : -1))));
                tmp[1] = ((cnv[1] >= '0' && cnv[1] <= '9') ? cnv[1] - '0'
                        : ((cnv[1] >= 'A' && cnv[1] <= 'F') ? cnv[1] - 'A' + 10
                                : (((cnv[1] >= 'a' && cnv[1] <= 'f') ? cnv[1] - 'a' + 10
                                        : -1))));
                if (tmp[0] == -1 || tmp[1] == -1) {
                    throw new IOException("指定引数は、数値(16進数)以外の情報が含まれています");
                }

                ret[i] = (byte) ((tmp[1] & 0x0000000f) | ((tmp[0] & 0x0000000f) << 4));
            }
        } finally {
            cnv = null;
            tmp = null;
        }
        return ret;
    }
}
