
var Validator = {
   check: function(field, reg, extra) {
      var response;
      var rule = this.rule;
      rule.field = field;
      rule.value = field.value;
      rule.extra = extra;

      if(!reg || !reg.match(/^!/))
         response = rule.input();

      if(reg && !response && rule.value != '') {
         reg = reg.replace(/^!/, '');

         var mode = reg.split(/\s+/);
         for(var i = 0, m; m = mode[i]; i++) {
            m = m.replace(/([\d\-]+)?$/, '');
            response = rule[m](RegExp.$1);
            if(response) break;
         }
      }

      if(response)
         this.baloon.open(field, response);
   },

   submit: function(form) {
      this.allclose(form);
      var btns = new Array;

      for (var i = 0, f; f = form[i]; i++) {
	  if (!f.disabled) {
	      if (f.onblur) {
		  f.onblur();
	      }
	      if (f.type == 'submit' || f.type == 'image') {
		  if (!f.className.match(/nobaloon/)) {
		      btns.push(f);
		  }
	      }
	  }
      }

      for (var i = 0, f, z; f = form[i]; i++) {
	  if (f._validbaloon && f._validbaloon.visible()) {
	      while (z = btns.shift())
		  this.baloon.open(z, this.rule.submit());
	      return false;
	  }
      }

      return true;
   },

   allclose: function(form) {
      for(var i = 0, f; f = form[i]; i++)
         if(f._validbaloon) f._validbaloon.close();
   }
};

Validator.baloon = {
    index: 11000,

   open: function(field, msg) {
      if(!field._validbaloon) {
         var obj = new this.element(field);
         obj.create();
         field._validbaloon = obj;
         if(field.type == 'radio' || field.type == 'checkbox') {
            for(var i = 0, e; e = field.form[field.name][i]; i++)
               addEvent(e, 'focus', function() { obj.close(); });
         }
      }

      field._validbaloon.show(msg);
   },

   element: function() {
      this.initialize.apply(this, arguments);
   }
};

Validator.baloon.element.prototype = {
   initialize: function(field) {
      this.parent = Validator.baloon;
      this.field = field;
   },

   create: function() {
      var field  = this.field;

      var box = document.createElement('div');
      box.className = 'baloon';

      var offset = Validator.Position.offset(field);
      var top  = offset.y - 25;
      var left = offset.x - 20 + field.offsetWidth;
      box.style.top  = top +'px';
      box.style.left = left+'px';

      var self = this;
      addEvent(box, 'click', function() { self.toTop(); });

      var bindClose = function() { self.close(); };
      var link = document.createElement('a');
      link.appendChild(document.createTextNode('X'));
      link.setAttribute('href', 'javascript:void(0);');
      addEvent(link, 'click', bindClose);
      addEvent(field, 'focus', bindClose);

      var msg = document.createElement('span');
      var div = document.createElement('div');
      div.appendChild(link);
      div.appendChild(msg);
      box.appendChild(div);

      document.body.appendChild(box);

      // iframe 挿入 (MSIE の SELECT z-index バグ対策)
      if (window.attachEvent && !window.opera) {
	  var es = this.field.form.elements;
	  for (var i = 0; i < es.length; i++) {
	      if (es[i].tagName.toLowerCase() == 'select') {
		  this.iframe = document.createElement('iframe');

		  this.iframe.src = 'javascript:false;';

		  this.iframe.style.position = 'absolute';
		  this.iframe.style.top = top + 'px';
		  this.iframe.style.left = left + 'px';
		  this.iframe.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(opacity=0);';

		  this.iframe.setAttribute('frameBorder', 0);
		  this.iframe.setAttribute('scrolling', 'no');
		  document.body.appendChild(this.iframe);
		  break;
	      }
	  }
      }

      this.box = box;
      this.msg = msg;
   },

   show: function(msg) {
      var field = this.field;
      this.msg.innerHTML  = msg;

      this.box.style.display = '';
      this.toTop();

      if (this.iframe) {
	  this.iframe.style.display = '';
	  this.iframe.style.width  = this.box.offsetWidth + 'px';
	  this.iframe.style.height = this.box.offsetHeight + 'px';
	  this.iframe.style.zIndex = this.box.style.zIndex - 1;
      }

      if(field.type != 'radio' && field.type != 'checkbox') {
         var colors = new Array('#FF6666', '#FFAAAA', '#FF6666', '#FFAAAA');
         window.setTimeout(function() {
            if(colors.length > 0) {
               field.style.backgroundColor = colors.shift();
               window.setTimeout(arguments.callee, 70);
            }
         }, 10);
      }
   },

   close: function() {
      this.box.style.display = 'none';
      this.field.style.backgroundColor = '';
      if (this.iframe) {
	  this.iframe.style.display = 'none';
      }
   },

   visible: function() {
      return (this.box.style.display == '');
   },

   toTop: function() {
	//this.box.style.zIndex = ++ this.parent.index;
	this.box.style.zIndex = this.parent.index + 1;
   }
};

Validator.rule = {
   msg: null,

   submit: function() {
      return this.msg.submit;
   },

   input: function() {
      if(this.field.type == 'radio' || this.field.type == 'checkbox') {
	  if (this.field.form[this.field.name] &&
	      this.field.form[this.field.name].checked) {
	      return;
	  }
	  for(var i = 0, e; e = this.field.form[this.field.name][i]; i++) {
            if(e.checked) return;
	  }
         return this.msg.noselect;
      } else if(this.value == '')
         return (this.field.type == 'select-one') ? this.msg.noselect : this.msg.noinput;
   },

   mail: function() {
    var pattern = ('^[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+' +
		   '@' +
		   '[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\\.' +
		   '[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$');
    var re = new RegExp(pattern);

    if (!this.value.match(re)) {
      return this.msg.mail;
    }

    //if(!this.value.match(/^[\x01-\x7F]+@((([-a-z0-9]+\.)*[a-z]+)|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))$/))
    // return this.msg.mail;
   },

   equal: function() {
      if(this.field.form[this.extra].value && this.value != this.field.form[this.extra].value)
         return this.msg.unequal;
   },

   alphabet: function() {
      if(!this.value.match(/^[a-zA-Z\-\d]+$/))
         return this.msg.alphabet;
   },

   checkany: function() {
    var pattern = this.extra;
    var re = new RegExp(pattern);

    for (var i = 0; i < this.field.form.length; i++) {
      if ('name' in this.field.form[i] &&
	  this.field.form[i].name.match(re) &&
	  this.field.form[i].checked) {
	return;
      }
    }
    return 'いずれかにチェックしてください';
   },

   // スペース(U+3000)、長音記号(U+30FC)、中黒(U+30FB)も含む。
   kana: function() {
    for(var i = 0;i < this.value.length;i++) {
      var code = this.value.charCodeAt(i);
      if (code == 0x3000 ||
	  0x30A1 <= code && code <= 0x30FF) {
	continue;
      }

      return this.msg.kana;
    }
   },

   // ひらがな
   // スペース(U+3000)を含む。
   // 長音記号(U+30FC)や中黒(U+30FB)は含まない。
   hiragana: function() {
     for (var i = 0; i < this.value.length; i++) {
       var ch = this.value.charAt(i);

       if (ch == ' ' || ch == '\u3000' || '\u3041' <= ch && ch <= '\u3093') {
	 // ' ': ホワイトスペース
	 // U+3000 = IDEOGRAPHIC SPACE (いわゆる全角スペース)
	 // カタカナ: U+30A1(ァ) ～ U+30F6(ヶ)
	 // ひらがな: U+3041(ぁ) ～ U+3093(ん)

       } else {
	 return 'ひらがなで入力してください';
       }
     }
   },

   count: function(arg) {
      return this._range(arg, this.value.length, this.msg.count);
   },
   
   sel_non1st: function() {
	var sel = this.field;
	var val1 = sel.options[0].value;
	if (this.value == val1) {
	    return '選択してください';
	}
	return '';
    },

   /// 正規表現でマッチングするかどうかで validate する。
   /// 例: onblur="Validator.check(this, 'regex', '^[\\w]+$')"
   /// このように、バックスラッシュをエスケープする必要があることに注意。
   regex: function() {
     var regex = new RegExp(this.extra);
     if (this.value.match(regex)) {
       return '';
     } else {
       return '値が正しくありません';
     }
   },

   ///
   /// 電話番号かどうか?
   /// 行頭、行末のホワイトスペースは取り除いてからチェックする。
   /// ハイフンの前後にはスペースを入れてよい。(例: 03 - 1234 - 5678)
   /// 全角文字には対応していない
   ///
   /// 例: onblur="Validator.check(this, 'phone', 'domestic')"
   ///
   /// 電話番号に正しくマッチする正規表現はかなり長くなる。
   /// http://blog.livedoor.jp/nipotan/archives/17526053.html
   /// 国際電話番号も考慮するといっそうややこしい。
   /// ここでは緩めの正規表現を使っている。
   ///
   phone: function() {
    var patterns;
    if (this.extra == 'domestic') {
      patterns = [
		  ('^' +
		   '\\s*\\d+\\s*-' + // 市外局番
		   '\\s*\\d+\\s*-' + // 市内局番
		   '\\s*\\d+\\s*' + // 番号
		   '$'),
		  ('^' +
		   '\\s*\\d+\\s*-' + // 市内局番
		   '\\s*\\d+\\s*' + // 番号
		   '$'),
		  ('^' +
		   '\\s*\\d+\\s*' + // 番号
		   '$')
		  ];

    } else { // 数字で始まって、数字/スペース/ハイフンが続き、数字で終わる
      patterns = [
		  '^\\s*\\d[\\s\\d-]*\\d\\s*$'
		  ];
    }

    var value = this.value.replace(/^\s+/, '').replace(/\s+$/, '');

    var re;
    for (var i = 0; i < patterns.length; i++) {
      re = new RegExp(patterns[i]);
      if (value.match(re)) {
	return '';
      }
    }

    return '電話番号を半角数字で入力してください';
   },

   num: function(arg) {
      if(!this.value.match(/^[\d]+$/))
         return this.msg.num.nonumber;

      return this._range(arg, parseInt(this.value), this.msg.num);
   },

   real: function(arg) {
	if(!this.value.match(/^[+-]?[\d]+(\.\d*)?$/))
	    return this.msg.real.nonumber;
   },

   check: function(arg) {
      var value = 0;
      if (this.field.form[this.field.name] &&
	  this.field.form[this.field.name].checked) {
	  value++;
      } else if (this.field.form[this.field.name] instanceof Array) {
	  for(var i = 0, e; e = this.field.form[this.field.name][i]; i++) {
	      if (e.checked) value += 1;
	  }
      }

      return this._range(arg, value, this.msg.check);
   },

   _range: function(range, value, msg) {
      if(!range) return;

      var result = '';
      var c = (" "+range).split(/\-/);
      var min = parseInt(c[0]) || 0;
      var max = parseInt(c[1]) || 0;
      var have_min = range.match(/\d-/);
      var have_max = range.match(/-\d/);
      var exact_match = range.match(/^\d+$/);

      if (exact_match) {
	  if (value != min) {
	      result = msg.unequal;
	  }
      } else if (have_min && have_max) {
	  if (value < min || max < value)  {
	      result = msg.outofrange;
	  }
      } else if (!have_min && have_max) {
	  if (max < value) {
	      result = msg.too_big;
	  }
      } else if (have_min && !have_max) {
	  if (value < min) {
		  result = msg.too_small;
	  }
      } else {
	  // invalid syntax
      }

      return result.replace(/%1/g, min).replace(/%2/g, max);
   }
};

Validator.lang = {
   ja: {
      noselect:   '\u9078\u629E\u304C\u5FC5\u8981\u3067\u3059\u3002',
      noinput:    '\u5165\u529B\u304C\u5FC5\u8981\u3067\u3059\u3002',
      unequal:    '\u5165\u529B\u304C\u63C3\u3063\u3066\u3044\u307E\u305B\u3093\u3002',
   
      submit:     '\u5165\u529B\u30A8\u30E9\u30FC\u304C\u3042\u308A\u307E\u3059\u3002',
      mail:       '\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093\u3002',
      alphabet:   '\u30A2\u30EB\u30D5\u30A1\u30D9\u30C3\u30C8\u3001\u6570\u5B57\u3001' +
                     '- \u4EE5\u5916\u306F\u5165\u529B\u51FA\u6765\u307E\u305B\u3093\u3002',
      kana:       '\u5168\u89D2\u30AB\u30BF\u30AB\u30CA\u3067\u5165\u529B\u3057\u3066\u4E0B\u3055\u3044\u3002',
   
      count: {
         unequal:    '%1'+'\u6587\u5B57\u3067\u5165\u529B\u3057\u3066\u4E0B\u3055\u3044\u3002',
         too_big:    '%2'+'\u6587\u5B57\u4EE5\u5185\u3067\u5165\u529B\u3057\u3066\u4E0B\u3055\u3044\u3002',
         too_small:  '%1'+'\u6587\u5B57\u4EE5\u4E0A\u5165\u529B\u3057\u3066\u4E0B\u3055\u3044\u3002',
         outofrange: '%1'+'\u304B\u3089'+'%2'+'\u6587\u5B57\u306E\u9593\u3067\u5165\u529B\u3057\u3066\u4E0B\u3055\u3044\u3002'
      },
   
      real: {
	    nonumber: '数値ではありません'
      },

      num: {
nonumber:   '\u6570\u5024\u3067\u5165\u529B\u3057\u3066\u4E0B\u3055\u3044\u3002',
         unequal:    '%1'+'\u3068\u5165\u529B\u3057\u3066\u4E0B\u3055\u3044\u3002',
         too_big:    '%2'+'\u4EE5\u4E0B\u306E\u5024\u3092\u5165\u529B\u3057\u3066\u4E0B\u3055\u3044\u3002',
         too_small:  '%1'+'\u4EE5\u4E0A\u306E\u5024\u3092\u5165\u529B\u3057\u3066\u4E0B\u3055\u3044\u3002',
         outofrange: '%1'+'\u304B\u3089'+'%2'+'\u306E\u9593\u3067\u5165\u529B\u3057\u3066\u4E0B\u3055\u3044\u3002'
      },
   
      check: {
         unequal:    '\u30C1\u30A7\u30C3\u30AF\u306F'+'%1'+'\u500B\u3057\u3066\u4E0B\u3055\u3044\u3002',
         too_big:    '\u30C1\u30A7\u30C3\u30AF\u306F'+'%2'+'\u500B\u307E\u3067\u3067\u3059\u3002',
         too_small:  '\u30C1\u30A7\u30C3\u30AF\u306F'+'%1'+'\u500B\u4EE5\u4E0A\u3057\u3066\u4E0B\u3055\u3044\u3002',
         outofrange: '\u30C1\u30A7\u30C3\u30AF\u306F'+'%1'+'\u500B\u304B\u3089'+'%2'+'\u500B\u307E\u3067\u3067\u3059\u3002'
      }
   }
};

Validator.rule.msg = Validator.lang.ja;


