// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

// couch.js, with modifications

// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

// some monkeypatches
var JSON = {
  parse : function(string) {
    return eval('('+string+')');
  },
  stringify : function(obj) {
    return toJSON(obj||null);
  }
};

RegExp.escape = function(text) {
  if (!arguments.callee.sRE) {
    var specials = [
      '/', '.', '*', '+', '?', '|',
      '(', ')', '[', ']', '{', '}', '\\'
    ];
    arguments.callee.sRE = new RegExp(
      '(\\' + specials.join('|\\') + ')', 'g'
    );
  }
  return text.replace(arguments.callee.sRE, '\\$1');
}

// This is a JS wrapper for the curl function made available in couch_js.c,
// it should be used in other JavaScripts that would like to make HTTP calls.

var HTTP = (function() {
  function parseCurl(string) {
    var parts = string.split(/\r\n\r\n/);
    var body = parts.pop();
    var header = parts.pop();
    var headers = header.split(/\n/);

    var status = /HTTP\/1.\d (\d*)/.exec(header)[1];
    return {
      responseText: body,
      status: parseInt(status),
      getResponseHeader: function(key) {
        var keymatcher = new RegExp(RegExp.escape(key), "i");
        for (var i in headers) {
          var h = headers[i];
          if (keymatcher.test(h)) {
            var value = h.substr(key.length+2);
            return value.replace(/^\s+|\s+$/g,"");
          }
        }
        return "";
      }
    }
  };
  return {
    GET : function(url, body, headers) {
      var st, urx = url, hx = (headers || null);
      st = gethttp(urx, hx);
      return parseCurl(st);
    },
    HEAD : function(url, body, headers) {
      var st, urx = url, hx = (headers || null);
      st = headhttp(urx, hx);
      return parseCurl(st);
    },
    DELETE : function(url, body, headers) {
      var st, urx = url, hx = (headers || null);
      st = delhttp(urx, hx);
      return parseCurl(st);
    },
    MOVE : function(url, body, headers) {
      var st, urx = url, hx = (headers || null);
      st = movehttp(urx, hx);
      return parseCurl(st);
    },
    COPY : function(url, body, headers) {
      var st, urx = url, hx = (headers || null);
      st = copyhttp(urx, hx);
      return parseCurl(st);
    },
    POST : function(url, body, headers) {
      var st, urx = url, bx = (body || ""), hx = (headers || {});
      hx['Content-Type'] = hx['Content-Type'] || "application/json";
      st = posthttp(urx, bx, hx);
      return parseCurl(st);
    },
    PUT : function(url, body, headers) {
      var st, urx = url, bx = (body || ""), hx = (headers || {});
      hx['Content-Type'] = hx['Content-Type'] || "application/json";
      st = puthttp(urx, bx, hx);
      return parseCurl(st);
    }
  };
})();

// Monkeypatches to CouchDB client for use of curl.

CouchDB.host = (typeof window == 'undefined' || !window) ? "127.0.0.1:5984" : window;

CouchDB.request = function(method, uri, options) {
  var full_uri = "http://" + CouchDB.host + uri;
  options = options || {};
  var response = HTTP[method](full_uri, options.body, options.headers);
  return response;
}


function toJSON(val) {
  if (typeof(val) == "undefined") {
    throw {error:"bad_value", reason:"Cannot encode 'undefined' value as JSON"};
  }
  var subs = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f',
              '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
  if (typeof(val) == "xml") { // E4X support
    val = val.toXMLString();
  }
  return {
    "Array": function(v) {
      var buf = [];
      for (var i = 0; i < v.length; i++) {
        buf.push(toJSON(v[i]));
      }
      return "[" + buf.join(",") + "]";
    },
    "Boolean": function(v) {
      return v.toString();
    },
    "Date": function(v) {
      var f = function(n) { return n < 10 ? '0' + n : n }
      return '"' + v.getUTCFullYear()   + '-' +
                 f(v.getUTCMonth() + 1) + '-' +
                 f(v.getUTCDate())      + 'T' +
                 f(v.getUTCHours())     + ':' +
                 f(v.getUTCMinutes())   + ':' +
                 f(v.getUTCSeconds())   + 'Z"';
    },
    "Number": function(v) {
      return isFinite(v) ? v.toString() : "null";
    },
    "Object": function(v) {
      if (v === null) return "null";
      var buf = [];
      for (var k in v) {
        if (!v.hasOwnProperty(k) || typeof(k) !== "string" || v[k] === undefined) {
          continue;
        }
        buf.push(toJSON(k, val) + ": " + toJSON(v[k]));
      }
      return "{" + buf.join(",") + "}";
    },
    "String": function(v) {
      if (/["\\\x00-\x1f]/.test(v)) {
        v = v.replace(/([\x00-\x1f\\"])/g, function(a, b) {
          var c = subs[b];
          if (c) return c;
          c = b.charCodeAt();
          return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
        });
      }
      return '"' + v + '"';
    }
  }[val != null ? val.constructor.name : "Object"](val);
}



// *************** Test Framework Console Adapter ****************** //

var p = print;
var numFailures = 0;

function runAllTestsConsole() {
  var numTests = 0;
  var debug = false;
  for (var t in couchTests) {
    p(t);
    if (t == "utf8") {
      p("We skip the utf8 test because it fails due to problems in couch_js.c");
      p("Run the in-browser tests to verify utf8.\n");
    } else {
      numTests += 1;
      var testFun = couchTests[t];
      runTestConsole(testFun, debug);
    }
  }
  p("Results: "+numFailures.toString() + " failures in "+numTests+" tests.")
};

function runTestConsole(testFun, debug) {
  var start = new Date().getTime();
  try {
    if (!debug) testFun = patchTest(testFun) || testFun;
    testFun();
    p("PASS");
  } catch(e) {
    p("ERROR");
    p("Exception raised: "+e.toString());
    p("Backtrace: "+e.stack);
  }
  var duration = new Date().getTime() - start;
  p(duration+"ms\n");
};


// Use T to perform a test that returns false on failure and if the test fails,
// display the line that failed.
// Example:
// T(MyValue==1);
function T(arg1, arg2) {
  if (!arg1) {
    p("Assertion failed: "+(arg2 != null ? arg2 : arg1).toString());
    numFailures += 1
  }
}

p("Running CouchDB Test Suite\n");
p("Host: "+CouchDB.host);

try {
  p("Version: "+CouchDB.getVersion()+"\n");
  runAllTestsConsole();
  // runTestConsole(tests.attachments);
} catch (e) {
  p(e.toString());
}

p("\nFinished");