/*
 * Subnetting.java -- Java Applet/application
 * Copyright (C) 2000  Petteri Kettunen  
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along 
 * with this program; if not, write to the Free Software Foundation, Inc., 
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA
 *
 */
/*
 * $Revision: 1.2 $
 *
 * Comments? Suggestions? -> petterik@iki.fi
 */

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.lang.*;
import java.net.*;
import java.util.StringTokenizer;
import java.math.*;

public class Subnetting extends Applet     
    implements ItemListener, ActionListener {
    TextArea taResult;
    TextField tfIP;
    Choice chNWBits, chSNBits;
    Checkbox cbZeroSN;
    Button calcButton;
    int iNWBits = 0, iSNBits = 0, iDefaultNWBits = 24;
    boolean bDefaultZeroSN = false; // affects only Applet
    final static String sVersion = "0.9b";

    // applet inits    
    public void init() {	
	setBackground(Color.white);
	chNWBits = new Choice();
	chSNBits = new Choice();
	taResult = new TextArea("", 24, 80);
	taResult.setFont(new Font("Courier", Font.PLAIN, 12));
	taResult.setText(getSplashText());
	for (int i = 0; i <= 32; i++) {
	    chNWBits.addItem("" + i);
	    chSNBits.addItem("" + i);
	}
	if (iDefaultNWBits > 0 && iDefaultNWBits <= 32) {
	    chNWBits.select(iNWBits = iDefaultNWBits);
	}
	//setLayout(new GridLayout(4));
	add(tfIP = new TextField("127.0.0.1", 18));
	add(new Label("NW Bits"));
	add(chNWBits);
	chNWBits.addItemListener(this);
	add(new Label("SN Bits"));
	add(chSNBits);
	chSNBits.addItemListener(this);
	add(cbZeroSN = new Checkbox("zero subnet", bDefaultZeroSN));
	add(calcButton = new Button("Calculate"));
	calcButton.addActionListener(this);
	add("South", taResult);
    }

    public String getAppletInfo() {
	String info = "Subnetting.java " + sVersion +
	    "\nby Petteri Kettunen <petterik@iki.fi>";
	return info;
    }
    
    /* TODO... public String[][] getParameterInfo() {
       String[][] info = {
	{"IMAGE_DIR", "string", "Image directory relative to Applet."},
	};
        return info;
	} */

    public void start() {
	//panel.start();
    }

    public void stop() {
	//panel.stop();
    }

    public void destroy() {
        //remove(panel);
        //remove(controls);
    }

    private static String getSplashText() {
	return "'Subnetting.java' version " + sVersion + " Copyright (C) 2000 Petteri Kettunen.\n\n'Subnetting.java' comes with ABSOLUTELY NO WARRANTY. This is free software,\nand you are welcome to redistribute it under certain conditions; see the\nsource code or URL http://www.iki.fi/petterik/ for details.\n\n";
    }

    // octet to 'bit' string 
    private static String o2b(int iOctet) {
	String res = "";
	int val = iOctet;

	for (int t = 128; t > 0; t /=2) {
	    if (val >= t) {
		res = res.concat("1");
		val -= t;
	    } else {
		res = res.concat("0");
	    }
	}
	return res;
    }
    
    // convert four bits to hex string
    private static String b2h(String sBits) {
	String res;
	int val = b2o(sBits);

	switch(val) {
	case 15: res = "f"; break;
	case 14: res = "e"; break;
	case 13: res = "d"; break;
	case 12: res = "c"; break;
	case 11: res = "b"; break;
	case 10: res = "a"; break;
	default: res = "" + val;
	}
	return res;
    }

    // 'bit' string to octet 
    private static int b2o(String sBits) {	
	if (sBits.length() == 4) {
	    return Integer.valueOf(sBits.substring(0, 1)).intValue() * 8 +
		Integer.valueOf(sBits.substring(1, 2)).intValue() * 4 +
		Integer.valueOf(sBits.substring(2, 3)).intValue() * 2 +
		Integer.valueOf(sBits.substring(3, 4)).intValue();	
	}
	return Integer.valueOf(sBits.substring(0,1)).intValue() * 128 +
	    Integer.valueOf(sBits.substring(1, 2)).intValue() * 64 +
	    Integer.valueOf(sBits.substring(2, 3)).intValue() * 32 +
	    Integer.valueOf(sBits.substring(3, 4)).intValue() * 16 +
	    Integer.valueOf(sBits.substring(4, 5)).intValue() * 8 +
	    Integer.valueOf(sBits.substring(5, 6)).intValue() * 4 +
	    Integer.valueOf(sBits.substring(6, 7)).intValue() * 2 +
	    Integer.valueOf(sBits.substring(7)).intValue();
    }

    // subnet mask: network and subnet bits 1, host bits 0
    private static String getNetmaskStr(int iNW, int iSN) {
	String res = "";
	int n = iNW + iSN, i = 0;
	
	while (i++ < n) {
	    res = res.concat("1");
	}
	while (i++ <= 32) {
	    res = res.concat("0");
	}
	return res;
    }

    // mask IP with network mask
    private static String getNetwork(String sIPBits, String sNWMaskBits) {
	String res = "";
	int i = 0;
	
	while (i < 32) {
	    if (sNWMaskBits.substring(i, i+1).equals("1")) {
		res = res.concat(sIPBits.substring(i, i+1));
	    } else {
		res = res.concat("0");
	    }
	    i++;
	}
	return res;
    }

    // subnet address: all host bits are 0
    private static String getSN(String sNWBits, String sSNBits) {
	String res = sNWBits + sSNBits;	

	while (res.length() < 32) {
	    res = res.concat("0");
	}
	return res;
    }

    // broadcast address: all host bits are 1
    private static String getBcast(String sNWBits, String sSNBits) {
	String res = sNWBits + sSNBits;	

	while (res.length() < 32) {
	    res = res.concat("1");
	}
	return res;
    }

    // first host: subnet address + 1
    private static String getFirstHost(String sSN) {
	return sSN.substring(0,24) + o2b(b2o(sSN.substring(24)) + 1);
    }

    // last host: broadcast - 1
    private static String getLastHost(String sBcast) {
	return sBcast.substring(0,24) + o2b(b2o(sBcast.substring(24)) - 1);
    }

    // make IP to human-readable format
    private static String decodeIP(String bits) {
	String [] octets = new String[4];
	String [] hex = new String[8];

	octets[0] = bits.substring(0,8);
	hex[0] = b2h(octets[0].substring(0,4));
	hex[1] = b2h(octets[0].substring(4));
	octets[1] = bits.substring(8,16);
	hex[2] = b2h(octets[1].substring(0,4));
	hex[3] = b2h(octets[1].substring(4));
	octets[2] = bits.substring(16,24);
	hex[4] = b2h(octets[2].substring(0,4));
	hex[5] = b2h(octets[2].substring(4));
	octets[3] = bits.substring(24,32);
	hex[6] = b2h(octets[3].substring(0,4));
	hex[7] = b2h(octets[3].substring(4));
	return octets[0] + "." + octets[1] + "." + octets[2] + "." +
	    octets[3] + " " + 
	    hex[0] + hex[1] + hex[2] + hex[3] + hex[4] + hex[5] +
	    hex[6] + hex[7] + " " 
	    + b2o(octets[0]) + "." + b2o(octets[1]) + 
	    "." + b2o(octets[2]) + "." + b2o(octets[3]);
    }

    // the main calculator function
    private static String calcSubnetting(String sIP, int iNW, int iSN, boolean bZeroSN) {
	int i, nSubnets;
	String res = "";
	String [] octet = new String[4];
	StringTokenizer st;
	int [] iOctet = new int[4];

	// catch some pathological cases...
	if (iNW < 0) {
	    return "ERROR: negative number of network bits\n";
	}
	if (iSN < 0) {
	    return "ERROR: negative number of subnet bits\n";
	}
	if (iNW == 0) {
	    return "ERROR: network bits equals to zero - no network?\n";
	}
	if ((iNW + iSN) > 32) {
	    return "ERROR: the sum of network and subnet bits is over 32\n";
	}
	// get the IP
	try {
	    st = new StringTokenizer(sIP, ".");
	    for (i = 0; i != 4; i++) {
		octet[i] = st.nextToken();	    
		iOctet[i] = Integer.valueOf(octet[i]).intValue();
	    }
	} catch (Exception e) {
	    return "ERROR: Invalid IP address string '" + sIP + "'\n";
	}
	// extract and check the values of octets
	for (i = 0; i != 4; i++) {
	    if (iOctet[i] < 0 || iOctet[i] > 255) {
		return "Invalid IP address '" + sIP + 
		    "' -- all octets should be in range 0-255\n";
	    }
	}
	String nmStr = getNetmaskStr(iNW, iSN);
	String nwMaskStr = getNetmaskStr(iNW, 0);
	String ipBits = o2b(iOctet[0]) +  o2b(iOctet[1]) + o2b(iOctet[2]) +
	    o2b(iOctet[3]);
	String nwStr = getNetwork(ipBits, nwMaskStr);
	int nSubs = (int)Math.pow(2, iSN);
	if (iSN == 0) {
	    // subnet calculation will not take place
	    nSubnets = 0;
	} else {
	    if (bZeroSN) {
		i = 0;
		nSubnets = nSubs;
	    } else {
		nSubs -= 1;
		i = 1;
		nSubnets = nSubs - 1;
	    }
	}
	res = res.concat("\nnetwork bits " + iNW + 
			 ", subnet bits " + iSN + 
			 ", zero subnet " + (bZeroSN ? "on" : "off") +
			 ", total subnets " + nSubnets + "\n\n");
	res = res.concat("network        " + decodeIP(nwStr) + "\n");
	res = res.concat("network mask   " + decodeIP(nwMaskStr) + "\n");
	//res = res.concat("IP address    " + decodeIP(ipBits) + "\n");
	res = res.concat("netmask        " + decodeIP(nmStr) + "\n\n");
	String stub = nwStr.substring(0, iNW);
	// catch this here so the applet can be used for checking netmask
	if (iSN == 0) {
	    res = res.concat("WARNING: subnet bits equals to zero - no subnetting?\n");
	    return res;
	}
	if (bZeroSN == false && iSN == 1) {
	    res = res.concat("ERROR: one subnet bit while zero subnet not allowed\n");
	    return res;
	}
	while (i < nSubs) {
	    String sSubnetPart = o2b(i).substring(8 - iSN);
	    String snAddress = getSN(stub, sSubnetPart);
	    String sFirstHost = getFirstHost(snAddress);
	    String bcAddress = getBcast(stub, sSubnetPart);
	    String sLastHost = getLastHost(bcAddress);
	    res = res.concat("  subnet  " + i + ":\n");
	    res = res.concat("    network    " + decodeIP(snAddress) + "\n");
	    res = res.concat("    first host " + decodeIP(sFirstHost) + "\n");
	    res = res.concat("    last host  " + decodeIP(sLastHost) + "\n");
	    res = res.concat("    broadcast  " + decodeIP(bcAddress) + "\n");
	    i++;
	}
	return res;
    }

    // check for choice events
    public void itemStateChanged(ItemEvent e) {
	if (e.getSource() == chNWBits) {
	    iNWBits = Integer.valueOf(chNWBits.getSelectedItem()).intValue();
	} else if (e.getSource() == chSNBits) {
	    iSNBits = Integer.valueOf(chSNBits.getSelectedItem()).intValue();
	}
    }

    // check for valid button events
    public void actionPerformed(ActionEvent ev) {
	String buttonLabel = ev.getActionCommand();

	if (buttonLabel.equals("Calculate")) {
	    taResult.setText(calcSubnetting(tfIP.getText().trim(), 
					      iNWBits, iSNBits, 
					      cbZeroSN.getState()));
	}
    }

    // entry point of the program when run as application
    public static void main(String[] args) {	
	if (args.length != 4) {
	    System.out.println("usage: java Subnetting <ip address> <network bits> <subnet bits> <subnet zero flag>");
	    System.exit(1);
	}
	String sIP = args[0];
	int iNW = Integer.valueOf(args[1]).intValue();
	int iSN = Integer.valueOf(args[2]).intValue();
	int snflag = Integer.valueOf(args[3]).intValue();
	System.out.print(getSplashText());
	System.out.print(calcSubnetting(sIP, iNW, iSN, (snflag != 0)));
    }
}
