/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Users.java
 * Project management tool: user management
 * Written by: Steven M. Rubin
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) 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 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) 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, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.project;

import com.sun.electric.tool.Job;
import com.sun.electric.util.TextUtils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Iterator;

/**
 * This is the user management part of the Project Management tool.
 */
public class Users
{
	/**
	 * There are two levels of security: low and medium.
	 * Medium security manages a list of user names/passwords and requires logging-in.
	 * It is only "medium" becuase the passwords are badly encrypted, and it is easy to add user names.
	 * Low security simply uses the user's name without questioning it.
	 */
	public static final boolean LOWSECURITY    = true;
    private static final String PUSERFILE      = "projectusers";

	/** the users */							private static HashMap<String,String> usersMap;

	/**
	 * Method to return the number of users in the user database.
	 * @return the number of users in the user database.
	 */
	public static int getNumUsers()
	{
		ensureUserList();
		return usersMap.size();
	}

	/**
	 * Method to return an Iterator over the users in the user database.
	 * @return an Iterator over the users in the user database.
	 */
	public static Iterator<String> getUsers()
	{
		ensureUserList();
		return usersMap.keySet().iterator();
	}

	/**
	 * Method to tell whether a user name is in the user database.
	 * @param user the user name.
	 * @return true if the user name is in the user database.
	 */
	public static boolean isExistingUser(String user)
	{
		ensureUserList();
		return usersMap.get(user) != null;
	}

	/**
	 * Method to remove a user name from the user database.
	 * @param user the user name to remove from the user database.
	 */
	public static void deleteUser(String user)
	{
		usersMap.remove(user);
		saveUserList();
	}

	/**
	 * Method to add a user to the user database.
	 * @param user the user name to add.
	 * @param encryptedPassword the encrypted password for the user.
	 */
	public static void addUser(String user, String encryptedPassword)
	{
		usersMap.put(user, encryptedPassword);
		saveUserList();
	}

	/**
	 * Method to return the encrypted password associated with a given user.
	 * @param user the user name.
	 * @return the user's encrypted password (null if not found).
	 */
	public static String getEncryptedPassword(String user)
	{
		return usersMap.get(user);
	}

	/**
	 * Method to change a user's encrypted password.
	 * @param user the user name.
	 * @param newEncryptedPassword the new encrypted password for the user.
	 */
	public static void changeEncryptedPassword(String user, String newEncryptedPassword)
	{
		usersMap.put(user, newEncryptedPassword);
		saveUserList();
	}

	/************************ USER SUPPORT ***********************/

	/**
	 * Method to ensuer that there is a valid user name.
	 * @return true if there is NO valid user name (also displays error message).
	 */
	static boolean needUserName()
	{
		Project.pmActive = true;
		if (Project.getCurrentUserName().length() == 0)
		{
			if (LOWSECURITY)
			{
				Project.setCurrentUserName(System.getProperty("user.name"));
				return false;
			}
			Job.getUserInterface().showErrorMessage(
				"You must select a user first (in the 'Project Management' panel of the Preferences dialog)",
				"No Valid User Name");
			return true;
		}
		return false;
	}

	private static void ensureUserList()
	{
		if (usersMap == null)
		{
			usersMap = new HashMap<String,String>();
			String userFile = Project.getRepositoryLocation() + File.separator + PUSERFILE;
			URL url = TextUtils.makeURLToFile(userFile);
			try
			{
				URLConnection urlCon = url.openConnection();
				InputStreamReader is = new InputStreamReader(urlCon.getInputStream());
				LineNumberReader lnr = new LineNumberReader(is);

				for(;;)
				{
					String userLine = lnr.readLine();
					if (userLine == null) break;
					int colonPos = userLine.indexOf(':');
					if (colonPos < 0)
					{
						System.out.println("Missing ':' in user file: " + userLine);
						break;
					}
					String userName = userLine.substring(0, colonPos);
					String encryptedPassword = userLine.substring(colonPos+1);
					usersMap.put(userName, encryptedPassword);
				}

				lnr.close();
			} catch (IOException e)
			{
				System.out.println("Creating new user database");
			}
		}
	}

	private static void saveUserList()
	{
		// write the file back
		String userFile = Project.getRepositoryLocation() + File.separator + PUSERFILE;
		try
		{
			PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter(userFile)));

			for(String userName : usersMap.keySet())
			{
				String encryptedPassword = usersMap.get(userName);
				printWriter.println(userName + ":" + encryptedPassword);
			}

			printWriter.close();
			System.out.println("Wrote " + userFile);
		} catch (IOException e)
		{
			System.out.println("Error writing " + userFile);
			return;
		}
	}

	private static final int ROTORSZ = 256;		/* a power of two */
	private static final int MASK =   (ROTORSZ-1);
	/**
	 * Method to encrypt a string in the most simple of ways.
	 * A one-rotor machine designed along the lines of Enigma but considerably trivialized.
	 * @param text the text to encrypt.
	 * @return an encrypted version of the text.
	 */
	public static String encryptPassword(String text)
	{
		// first setup the machine
		String key = "BicIsSchediwy";
		String readable = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-";
		int seed = 123;
		int keyLen = key.length();
		for (int i=0; i<keyLen; i++) seed = seed*key.charAt(i) + i;
		char [] t1 = new char[ROTORSZ];
		char [] t2 = new char[ROTORSZ];
		char [] t3 = new char[ROTORSZ];
		char [] deck = new char[ROTORSZ];
		for(int i=0; i<ROTORSZ; i++)
		{
			t1[i] = (char)i;
			t3[i] = 0;
			deck[i] = (char)i;
		}
		for(int i=0; i<ROTORSZ; i++)
		{
			seed = 5*seed + key.charAt(i%keyLen);
			int random = seed % 65521;
			int k = ROTORSZ-1 - i;
			int ic = (random&MASK) % (k+1);
			random >>= 8;
			int temp = t1[k];
			t1[k] = t1[ic];
			t1[ic] = (char)temp;
			if (t3[k] != 0) continue;
			ic = (random&MASK) % k;
			while (t3[ic] != 0) ic = (ic+1) % k;
			t3[k] = (char)ic;
			t3[ic] = (char)k;
		}
		for(int i=0; i<ROTORSZ; i++) t2[t1[i]&MASK] = (char)i;

		// now run the machine
		int n1 = 0;
		int n2 = 0;
		int nr2 = 0;
		StringBuffer result = new StringBuffer();
		for(int pt=0; pt<text.length(); pt++)
		{
			int nr1 = deck[n1]&MASK;
			nr2 = deck[nr1]&MASK;
			int i = t2[(t3[(t1[(text.charAt(pt)+nr1)&MASK]+nr2)&MASK]-nr2)&MASK]-nr1;
			result.append(readable.charAt(i&63));
			n1++;
			if (n1 == ROTORSZ)
			{
				n1 = 0;
				n2++;
				if (n2 == ROTORSZ) n2 = 0;
				shuffle(deck, key);
			}
		}
		String res = result.toString();
		return res;
	}

	private static void shuffle(char [] deck, String key)
	{
		int seed = 123;
		int keyLen = key.length();
		for(int i=0; i<ROTORSZ; i++)
		{
			seed = 5*seed + key.charAt(i%keyLen);
			int random = seed % 65521;
			int k = ROTORSZ-1 - i;
			int ic = (random&MASK) % (k+1);
			int temp = deck[k];
			deck[k] = deck[ic];
			deck[ic] = (char)temp;
		}
	}

}
