/*
 * Copyright (C) 2006 Helge Hess <helge.hess@opengroupware.org>
 * Copyright (C) 2006 Marcus Mueller <znek@mulle-kybernetik.com>
 * 
 * This file is part of JOPE.
 * 
 * JOPE is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2, or (at your option) any later version.
 * 
 * JOPE 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 Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with JOPE; see the file COPYING. If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package org.opengroupware.jope.foundation;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;

/*
 * UString
 * 
 * Helper methods for the String object.
 * 
 * Things which are covered by Java:
 * 
 *   isdigit() => Character.isDigit()
 *   isspace() => Character.isWhitespace()
 *   
 *   componentsSeparatedByDelimiter() => String.split()
 */
public class UString {

  // Note: use String.split instead of componentsSeparatedByDelimiter
  
  public static String componentsJoinedByString(List _l, String _sep) {
    int len;
    
    if (_l == null)
      return null;
    
    if ((len = _l.size()) == 0)
      return "";
    if (len == 1)
      return (String)_l.get(0);
    
    StringBuffer sb = new StringBuffer(len * 10); 
    for (int i = 0; i < len; i++) {
      if (i > 0) sb.append(_sep);
      sb.append(_l.get(i));
    }
    return sb.toString();
  }
  public static String componentsJoinedByString(String[] _l, String _sep) {
    int len;
    
    if (_l == null)
      return null;
    
    if ((len = _l.length) == 0)
      return "";
    if (len == 1)
      return _l[0];
    
    StringBuffer sb = new StringBuffer(len * 10); 
    for (int i = 0; i < len; i++) {
      if (i > 0) sb.append(_sep);
      sb.append(_l[i]);
    }
    return sb.toString();
  }
  
  public static String replaceInSequence(String _s, String[] _patterns) {
    if (_s == null) return _s;
    if (_patterns == null || _patterns.length == 0) return _s;
    
    // TODO: improve me with a faster version ...
    for (int i = 0; i < _patterns.length; i += 2)
      _s = _s.replace(_patterns[i], _patterns[i + 1]);
    return _s;
  }
  
  /* XML escaping (no Java function for that???) */
  
  public static String stringByEscapingXMLString(String _s) {
    if (_s == null)
      return null;
    
    char[] chars = _s.toCharArray();
    int    len   = chars.length;
    if (len == 0)
      return "";
    
    int escapeCount = 0;
    for (int i = 0; i < len; i++) {
      switch (chars[i]) {
        case '&':  escapeCount += 5; break;
        case '<':  escapeCount += 4; break;
        case '>':  escapeCount += 4; break;
        case '"':  escapeCount += 7; break;
        case '\'': escapeCount += 6; break;
        default:
          break;
      }
    }
    if (escapeCount == 0)
      return _s;
    
    char[] echars = new char[len + escapeCount];
    int j = 0;
    for (int i = 0; i < len; i++) {
      switch (chars[i]) {
        case '&':
          echars[j] = '&'; j++; echars[j] = 'a'; j++; echars[j] = 'm'; j++;
          echars[j] = 'p'; j++; echars[j] = ';'; j++;
          break;
        case '<':
          echars[j] = '&'; j++; echars[j] = 'l'; j++; echars[j] = 't'; j++;
          echars[j] = ';'; j++;
          break;
        case '>':
          echars[j] = '&'; j++; echars[j] = 'g'; j++; echars[j] = 't'; j++;
          echars[j] = ';'; j++;
          break;
        case '"':
          echars[j] = '&'; j++; echars[j] = 'q'; j++; echars[j] = 'u'; j++;
          echars[j] = 'o'; j++; echars[j] = 't'; j++; echars[j] = ';'; j++;
          break;
        case '\'':
          echars[j] = '&'; j++; echars[j] = 'a'; j++; echars[j] = 'p'; j++;
          echars[j] = 'o'; j++; echars[j] = 's'; j++; echars[j] = ';'; j++;
          break;

        default:
          echars[j] = chars[i];
          j++;
          break;
      }
    }
    
    return new String(echars, 0, j);
  }

  /* HTML escaping */
  
  public static String stringByEscapingHTMLString(String _s) {
    if (_s == null)
      return null;
    
    char[] chars = _s.toCharArray();
    int    len   = chars.length;
    if (len == 0)
      return "";
    
    int escapeCount = 0;
    for (int i = 0; i < len; i++) {
      switch (chars[i]) {
        case '&': escapeCount += 5; break;
        case '<': escapeCount += 4; break;
        case '>': escapeCount += 4; break;
        case '"': escapeCount += 7; break;
        default:
          if (chars[i] > 127) escapeCount += 8;
          break;
      }
    }
    if (escapeCount == 0)
      return _s;
    
    char[] echars = new char[len + escapeCount];
    int j = 0;
    for (int i = 0; i < len; i++) {
      switch (chars[i]) {
        case '&':
          echars[j] = '&'; j++; echars[j] = 'a'; j++; echars[j] = 'm'; j++;
          echars[j] = 'p'; j++; echars[j] = ';'; j++;
          break;
        case '<':
          echars[j] = '&'; j++; echars[j] = 'l'; j++; echars[j] = 't'; j++;
          echars[j] = ';'; j++;
          break;
        case '>':
          echars[j] = '&'; j++; echars[j] = 'g'; j++; echars[j] = 't'; j++;
          echars[j] = ';'; j++;
          break;
        case '"':
          echars[j] = '&'; j++; echars[j] = 'q'; j++; echars[j] = 'u'; j++;
          echars[j] = 'o'; j++; echars[j] = 't'; j++; echars[j] = ';'; j++;
          break;

        case 223: /* szlig */
          echars[j] = '&'; j++; echars[j] = 's'; j++; echars[j] = 'z'; j++;
          echars[j] = 'l'; j++; echars[j] = 'i'; j++; echars[j] = 'g'; j++;
          echars[j] = ';'; j++;
          break;

        case 252: /* uuml */
          echars[j] = '&'; j++; echars[j] = 'u'; j++; echars[j] = 'u'; j++;
          echars[j] = 'm'; j++; echars[j] = 'l'; j++; echars[j] = ';'; j++;
          break;
        case 220: /* Uuml */
          echars[j] = '&'; j++; echars[j] = 'U'; j++; echars[j] = 'u'; j++;
          echars[j] = 'm'; j++; echars[j] = 'l'; j++; echars[j] = ';'; j++;
          break;
        case 228: /* auml */
          echars[j] = '&'; j++; echars[j] = 'a'; j++; echars[j] = 'u'; j++;
          echars[j] = 'm'; j++; echars[j] = 'l'; j++; echars[j] = ';'; j++;
          break;
        case 196: /* Auml */
          echars[j] = '&'; j++; echars[j] = 'A'; j++; echars[j] = 'u'; j++;
          echars[j] = 'm'; j++; echars[j] = 'l'; j++; echars[j] = ';'; j++;
          break;
        case 246: /* ouml */
          echars[j] = '&'; j++; echars[j] = 'o'; j++; echars[j] = 'u'; j++;
          echars[j] = 'm'; j++; echars[j] = 'l'; j++; echars[j] = ';'; j++;
          break;
        case 214: /* Ouml */
          echars[j] = '&'; j++; echars[j] = 'O'; j++; echars[j] = 'u'; j++;
          echars[j] = 'm'; j++; echars[j] = 'l'; j++; echars[j] = ';'; j++;
          break;

        default:
          echars[j] = chars[i];
          j++;
          break;
      }
    }
    
    return new String(echars, 0, j);
  }
  
  public static String stringByEscapingHTMLAttributeValue(String _s) {
    if (_s == null)
      return null;
    
    char[] chars = _s.toCharArray();
    int    len   = chars.length;
    if (len == 0)
      return "";
    
    int escapeCount = 0;
    for (int i = 0; i < len; i++) {
      switch (chars[i]) {
        case '&':  escapeCount += 5; break;
        case '<':  escapeCount += 4; break;
        case '>':  escapeCount += 4; break;
        case '"':  escapeCount += 7; break;
        case '\t': escapeCount += 4; break;
        case '\n': escapeCount += 5; break;
        case '\r': escapeCount += 5; break;
        default:
          if (chars[i] > 127) escapeCount += 8;
          break;
      }
    }
    if (escapeCount == 0)
      return _s;
    
    char[] echars = new char[len + escapeCount];
    int j = 0;
    for (int i = 0; i < len; i++) {
      switch (chars[i]) {
        case '&':
          echars[j] = '&'; j++; echars[j] = 'a'; j++; echars[j] = 'm'; j++;
          echars[j] = 'p'; j++; echars[j] = ';'; j++;
          break;
        case '<':
          echars[j] = '&'; j++; echars[j] = 'l'; j++; echars[j] = 't'; j++;
          echars[j] = ';'; j++;
          break;
        case '>':
          echars[j] = '&'; j++; echars[j] = 'g'; j++; echars[j] = 't'; j++;
          echars[j] = ';'; j++;
          break;
        case '"':
          echars[j] = '&'; j++; echars[j] = 'q'; j++; echars[j] = 'u'; j++;
          echars[j] = 'o'; j++; echars[j] = 't'; j++; echars[j] = 'e'; j++;
          echars[j] = ';'; j++;
          break;

        case '\t':
          echars[j] = '&'; j++; echars[j] = '#'; j++; echars[j] = '9'; j++;
          echars[j] = ';'; j++;
          break;
        case '\n':
          echars[j] = '&'; j++; echars[j] = '#'; j++; echars[j] = '1'; j++;
          echars[j] = '0'; echars[j] = ';'; j++;
          break;
        case '\r':
          echars[j] = '&'; j++; echars[j] = '#'; j++; echars[j] = '1'; j++;
          echars[j] = '3'; j++; echars[j] = ';'; j++;
          break;

        case 223: /* szlig */
          echars[j] = '&'; j++; echars[j] = 's'; j++; echars[j] = 'z'; j++;
          echars[j] = 'l'; j++; echars[j] = 'i'; j++; echars[j] = 'g'; j++;
          echars[j] = ';'; j++;
          break;

        case 252: /* uuml */
          echars[j] = '&'; j++; echars[j] = 'u'; j++; echars[j] = 'u'; j++;
          echars[j] = 'm'; j++; echars[j] = 'l'; j++; echars[j] = ';'; j++;
          break;
        case 220: /* Uuml */
          echars[j] = '&'; j++; echars[j] = 'U'; j++; echars[j] = 'u'; j++;
          echars[j] = 'm'; j++; echars[j] = 'l'; j++; echars[j] = ';'; j++;
          break;
        case 228: /* auml */
          echars[j] = '&'; j++; echars[j] = 'a'; j++; echars[j] = 'u'; j++;
          echars[j] = 'm'; j++; echars[j] = 'l'; j++; echars[j] = ';'; j++;
          break;
        case 196: /* Auml */
          echars[j] = '&'; j++; echars[j] = 'A'; j++; echars[j] = 'u'; j++;
          echars[j] = 'm'; j++; echars[j] = 'l'; j++; echars[j] = ';'; j++;
          break;
        case 246: /* ouml */
          echars[j] = '&'; j++; echars[j] = 'o'; j++; echars[j] = 'u'; j++;
          echars[j] = 'm'; j++; echars[j] = 'l'; j++; echars[j] = ';'; j++;
          break;
        case 214: /* Ouml */
          echars[j] = '&'; j++; echars[j] = 'O'; j++; echars[j] = 'u'; j++;
          echars[j] = 'm'; j++; echars[j] = 'l'; j++; echars[j] = ';'; j++;
          break;

        default:
          echars[j] = chars[i];
          j++;
          break;
      }
    }
    
    return new String(echars, 0, j);
  }
  
  /* lower/upper */
  
  public static String capitalizedString(String _s) {
    if (_s == null) return null;
    
    char[] chars = _s.toCharArray();
    if (chars.length == 0) return "";
    
    boolean newWord = true;
    for (int i = 0; i < chars.length; i++) {
      if (Character.isWhitespace(chars[i])) {
        newWord = true;
        continue;
      }
      
      if (newWord) {
        chars[i] = Character.toUpperCase(chars[i]);
        newWord = false;
      }
    }
    return new String(chars);
  }
  
  /* hashing */
  
  public static String md5HashForString(String _p) {
    if (_p == null) return null;
    
    String pwdhash = null;
    try {
      // TODO: cash digest in thread local variable?
      MessageDigest md5 = MessageDigest.getInstance("MD5");
      md5.update(_p.getBytes("utf8"));
      
      byte[] bytehash = md5.digest();
      StringBuffer hexString = new StringBuffer();
      for (int i = 0; i < bytehash.length; i++) {
        String s = Integer.toHexString(0xFF & bytehash[i]);
        if (s.length() == 1) s = "0" + s;
        hexString.append(s);
      }
      md5.reset();

      pwdhash = hexString.toString();
    }
    catch (NoSuchAlgorithmException e) {
      System.err.println("Did not find MD5 hash generator!");
      return null;
    }
    catch (UnsupportedEncodingException e) {
      System.err.println("Encoding error when MD5 encoding a string!");
      return null;
    }
    
    if (pwdhash == null || pwdhash.length() == 0) {
      System.err.println("Could not compute an MD5 hash.");
      return null;
    }
    return pwdhash;
  }
}
