/*
 * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source 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.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *   Free SoftwareFoundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.es;

import java.io.*;
import java.net.*;
import java.util.*;

import com.caucho.util.*;
import com.caucho.vfs.*;
import com.caucho.es.parser.*;

/**
 * JavaScript object
 */
class NativeGlobal extends Native {
  static final int EVAL = 2;
  static final int PARSE_INT = 3;
  static final int PARSE_FLOAT = 4;
  static final int ESCAPE = 5;
  static final int UNESCAPE = 6;
  static final int IS_NAN = 7;
  static final int IS_FINITE = 8;

  static final int PRINT = 9;
  static final int SYSTEM = PRINT + 1;

  /**
   * Create a new object based on a prototype
   */
  NativeGlobal(String name, int n, int len)
  {
    super(name, len);

    this.n = n;
  }

  /**
   * Creates the native Object object
   */
  static void create(Global resin)
  {
    put(resin, "eval", EVAL, 1);
    put(resin, "parseInt", PARSE_INT, 2);
    put(resin, "parseFloat", PARSE_FLOAT, 1);
    put(resin, "escape", ESCAPE, 1);
    put(resin, "unescape", UNESCAPE, 1);
    put(resin, "isNaN", IS_NAN, 1);
    put(resin, "isFinite", IS_FINITE, 1);

    put(resin, "print", PRINT, 1);
    put(resin, "system", SYSTEM, 1);
  }

  private static void put(Global resin, String name, int n, int len)
  {
    ESId id = ESId.intern(name);

    resin.addProperty(id, new NativeGlobal(name, n, len));
  }

  public ESBase call(Call eval, int length) throws Throwable
  {
    switch (n) {
    case EVAL:
      return eval(eval, length);

    case PARSE_INT:
      return parseInt(eval, length);

    case PARSE_FLOAT:
      if (length < 1)
	return ESNumber.NaN;
      else {
	return ESNumber.create(ESString.parseFloat(eval.getArg(0).toStr()));
      }

    case ESCAPE:
      return escape(eval, length);

    case UNESCAPE:
      return unescape(eval, length);

    case IS_NAN:
      if (length < 1)
	return esUndefined;
      else
	return ESBoolean.create(Double.isNaN(eval.getArg(0).toNum()));

    case IS_FINITE:
      if (length < 1)
	return esUndefined;
      else {
	double value = eval.getArg(0).toNum();
	if (Double.isNaN(value))
	  return ESBoolean.create(false);
	else if (value == 1.0/0.0)
	  return ESBoolean.create(false);
	else if (value == -1.0/0.0)
	  return ESBoolean.create(false);
	else
	  return ESBoolean.create(true);
      }

    case PRINT:
      System.out.print(eval.getArg(0).toStr().toString());
      return esNull;

    case SYSTEM:
      String arg = eval.getArg(0).toStr().toString();
      String []args = new String[3];
      Process process;
      try {
	args[0] = "sh";
	args[1] = "-c";
	args[2] = arg;
	process = Runtime.getRuntime().exec(args);
	return ESNumber.create(process.waitFor());
      } catch (Exception e) {
	throw new ESWrapperException(e);
      }

    default:
      throw new ESException("Unknown object function");
    }
  }

  private ESBase eval(Call eval, int length) throws Throwable
  {
    if (length < 1)
      return esUndefined;

    ESBase arg = eval.getArg(0);
    if (! (arg instanceof ESString))
      return arg;

    String string = arg.toString();

    Global resin = Global.getGlobalProto();
    ESBase context = eval.getFunctionContext();
    Script script = null;
    ReadStream is = null;
    try {
      Parser parser = new Parser();
      is = Vfs.openString(string);
      script = parser.parseEval(is, "eval", 1);
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (is != null)
        is.close();
    }

    ESCallable jsClass = script.initClass(resin, eval.getGlobal());
    
    return jsClass.call(2, eval.caller, 0);
  }

  private ESBase parseInt(Call eval, int length) throws Throwable
  {
    if (length < 1)
      return ESNumber.NaN;

    ESString string = eval.getArg(0).toStr();
    int len = string.length();

    int i = 0;
    for (; i < len && Character.isSpaceChar(string.charAt(i)); i++) {
    }

    int sign = 1;
    if (i < len && string.charAt(i) == '+') {
      i++;
    } else if (i < len && string.charAt(i) == '-') {
      sign = -1;
      i++;
    }

    int radix = 0;
    if (length > 1) {
      radix = eval.getArg(1).toInt32();
      if (radix == 0) {
      }
      else if (radix < 2 || radix > 36)
	return ESNumber.NaN;
      else if (radix == 16 && i + 1 < length &&
	       string.charAt(i) == '0' &&
	       (string.charAt(i + 1) == 'x' ||
		string.charAt(i + 1) == 'X'))
	i += 2;
    }
    
    if (radix != 0) {
    }
    else if (i >= len)
      radix = 10;
    else if (string.charAt(i) != '0')
      radix = 10;
    else if (i + 1 < len && 
	     (string.charAt(i + 1) == 'x' || string.charAt(i + 1) == 'X')) {
      radix = 16;
      i += 2;
    } else {
      radix = 8;
    }

    long value = 0;
    boolean hasDigit = false;
    for (; i < len; i++) {
      int ch = string.charAt(i);

      if (radix <= 10 && ('0' <= ch && ch <= '0' + radix - 1)) {
	value = radix * value + string.charAt(i) - '0';
	hasDigit = true;
      } else if (radix > 10 && ('0' <= ch && ch <= '9')) {
	value = radix * value + string.charAt(i) - '0';
	hasDigit = true;
      } else if (radix > 10 && ('a' <= ch && ch <= 'a' + radix - 11)) {
	value = radix * value + string.charAt(i) - 'a' + 10;
	hasDigit = true;
      } else if (radix > 10 && ('A' <= ch && ch <= 'A' + radix - 11)) {
	value = radix * value + string.charAt(i) - 'A' + 10;
	hasDigit = true;
      } else
	break;
    }

    if (hasDigit)
      return ESNumber.create((double) sign * value);
    else
      return ESNumber.NaN;
  }

  static ESBase escape(Call eval, int length) throws Throwable
  {
    if (length < 1)
      return esUndefined;
    
    ESString string = eval.getArg(0).toStr();
    StringBuffer sbuf = new StringBuffer();

    for (int i = 0; i < string.length(); i++) {
      int ch = string.charAt(i);

      if (ch >= 'a' && ch <= 'z' ||
	  ch >= 'A' && ch <= 'Z' ||
	  ch >= '0' && ch <= '9' ||
	  ch == '@' || ch == '*' || ch == '.' || ch == '_' ||
	  ch == '+' || ch == '-' || ch == '/') {
	sbuf.append((char) ch);
      } else if (ch < 256) {
	sbuf.append('%');
	sbuf.append(Integer.toHexString(ch >> 4));
	sbuf.append(Integer.toHexString(ch & 0xf));
      } else {
	sbuf.append("%u");
	sbuf.append(Integer.toHexString(ch >> 12));
	sbuf.append(Integer.toHexString((ch >> 8) & 0xf));
	sbuf.append(Integer.toHexString((ch >> 4) & 0xf));
	sbuf.append(Integer.toHexString(ch & 0xf));
      }
    } 

    return ESString.create(sbuf.toString());
  }

  static ESBase unescape(Call eval, int length) throws Throwable
  {
    if (length < 1)
      return esUndefined;
    
    ESString string = eval.getArg(0).toStr();
    StringBuffer sbuf = new StringBuffer();

    for (int i = 0; i < string.length(); i++) {
      int ch = string.charAt(i);

      if (ch == '%' && i + 2 < string.length()) {
	int limit = 2;
	int start = 1;

	if (string.charAt(i + 1) == 'u') {
	  limit = 4;
	  start = 2;
	}

	int newCh = 0;
	int j = 0;
	for (; j < limit && i + j + start < string.length(); j++) {
	  if ((ch = string.charAt(i + j + start)) >= '0' && ch <= '9')
	    newCh = 16 * newCh + ch - '0';
	  else if (ch >= 'a' && ch <= 'f')
	    newCh = 16 * newCh + ch - 'a' + 10;
	  else if (ch >= 'A' && ch <= 'F')
	    newCh = 16 * newCh + ch - 'A' + 10;
	  else
	    break;
	}

	if (j != limit) {
	  sbuf.append('%');
	} else {
	  sbuf.append((char) newCh);
	  i += limit + start - 1;
	}
      }
      else
	sbuf.append((char) ch);
    }

    return ESString.create(sbuf.toString());
  }
}
