var Promise = require('bluebird');

module.exports = Parser;

function ParseError (val) {
  if (typeof val === 'string') {
    this.expecting = [val];
  } else {
    this.expecting = val;
  }
}

/* Wrapper for use in catch blocks whcih should
 * catch ParseError but not other errors */
filterParseError = function (fn) {
  return function (err) {
    if (err instanceof ParseError) {
      return fn(err);
    } else {
      throw err;
    }
  }
};


function Parser () {
  if (! this instanceof Parser) {
    return new Parser();
  }
}

Parser.prototype.parse = function () {
  throw (new Error('Impliment in subclass'));
};

Parser.prototype.map = function (fn) {
  var fiddled = new Parser();
  var self = this;
  fiddled.parse = function (str) {
    return self.parse(str)
    .then(function (res) {
      return {
        matched: res.matched.map(fn),
        remaining: res.remaining
      }
    });
  };
  return fiddled;
};

Parser.prototype.andReturn = function (val) {
  var fiddled = new Parser();
  var self = this;

  fiddled.parse = function (str) {
    return self.parse(str)
    .then(function (res) {
      return {
        matched: [val],
        remaining: res.remaining
      }
    });
  };

  return fiddled;
};

Parser.prototype.followedBy = function (next) {
  var combined = new Parser(),
      self = this;

  combined.parse = function (str) {
    var prefixPromise = self.parse(str),
        matchedFirst;

    return prefixPromise
    .then(function (prefixMatch) {
      matchedFirst = prefixMatch.matched;
      return next.parse(prefixMatch.remaining);
    })
    .then(function (suffixMatch) {
      return {
        matched: matchedFirst.concat(suffixMatch.matched),
        remaining: suffixMatch.remaining
      };
    })
    /* do not catch - if promise rejects return rejection */
  };

  return combined;
};

Parser.prototype.squash = function () {
  var modified = new Parser()
      self = this;
  modified.parse = function (str) {
    return self.parse(str)
    .then(function (res) {
      return {
        matched: [],
        remaining: res.remaining
      }
    });
  };
  return modified;
};

Parser.prototype.or = function (alt) {
  var combined = new Parser(),
      self = this;

  combined.parse = function (str) {
    var errors;

    return self.parse(str)
    .catch(filterParseError(function (err) {
      errors = err.expecting;
      return alt.parse(str);
    }))
    .catch(filterParseError(function (err) {
      return Promise.reject(new ParseError(errors.concat(err.expecting)));
    }))
  };

  return combined;
};

Parser.matchStr = function (matchStr) {
  var matcher = new Parser();
  matcher.parse = function (str) {
    if (str.slice(0, matchStr.length) === matchStr) {
      return Promise.resolve({
        matched: [matchStr],
        remaining: str.slice(matchStr.length)
      });
    } else {
      return Promise.reject(new ParseError(matchStr));
    }
  };
  return matcher;
};

Parser.numericChar = function () {
  var matcher = new Parser();
  matcher.parse = function (str) {
    if (str[0] >= '0' && str[0] <= '9') {
      return Promise.resolve({
        matched: [str[0]],
        remaining: str.slice(1)
      });
    } else {
      return Promise.reject(new ParseError(['Numeral 0,..,9']));
    }
  };
  return matcher;
};

Parser.alphaChar = function () {
  var matcher = new Parser();
  matcher.parse = function (str) {
    if ((str[0] >= 'a' && str[0] <= 'z') || (str[0] >= 'A' && str[0] <= 'Z')) {
      return Promise.resolve({
        matched: [str[0]],
        remaining: str.slice(1)
      });
    } else {
      return Promise.reject(new ParseError('A..Z,a..z'));
    }
  };
  return matcher;
};


Parser.many = function (m) {
  var matcher = new Parser(),
      first;
  matcher.parse = function (str) {
    return m.parse(str)
    .then(function (res) {
      first = res.matched;
      return Parser.many(m).parse(res.remaining)
    })
    .then(function (res) {
      return {
        matched: first.concat(res.matched),
        remaining: res.remaining
      }
    })
    .catch(filterParseError(function (err) {
      return {
        matched: [],
        remaining: str
      }
    }));
  };
  return matcher;
};

Parser.maybe = function (m) {
  var matcher = new Parser();

  matcher.parse = function (str) {
    return m.parse(str)
    .catch(filterParseError(function (err) {
      return {
        matched: [],
        remaining: str
      }
    }));
  };

  return matcher;
};

Parser.sepBy = function (sep, m) {
  var compound = sep.squash().followedBy(m);
  return m.followedBy(Parser.many(compound));
}

@@ -191,66 +191,8 @@
 %7D%0A%7D%0A
-%0A/* Wrapper for use in catch blocks whcih should%0A * catch 
 Pars
@@ -201,196 +201,52 @@
 rror
- but n
+.pr
 ot
- 
 ot
-her errors */%0AfilterParseError = function (fn) %7B%0A  return function (err) %7B%0A    if (err instanceof ParseError) %7B%0A      return fn(err);%0A    %7D else %7B%0A      throw err;%0A    %7D%0A  %7D%0A%7D;%0A
+ype = Object.create(Error.prototype);
 %0A%0Afu
@@ -2103,38 +2103,32 @@
 str)%0A    .catch(
-filter
 ParseError(funct
@@ -2113,33 +2113,34 @@
 catch(ParseError
-(
+, 
 function (err) %7B
@@ -2205,17 +2205,16 @@
 ;%0A    %7D)
-)
 %0A    .ca
@@ -2209,38 +2209,32 @@
   %7D)%0A    .catch(
-filter
 ParseError(funct
@@ -2219,33 +2219,34 @@
 catch(ParseError
-(
+, 
 function (err) %7B
@@ -2327,17 +2327,17 @@
 ;%0A    %7D)
-)
+;
 %0A  %7D;%0A%0A 
@@ -3848,38 +3848,32 @@
   %7D)%0A    .catch(
-filter
 ParseError(funct
@@ -3858,33 +3858,34 @@
 catch(ParseError
-(
+, 
 function (err) %7B
@@ -3954,25 +3954,24 @@
     %7D%0A    %7D)
-)
 ;%0A  %7D;%0A  ret
@@ -4122,14 +4122,8 @@
 tch(
-filter
 Pars
@@ -4128,17 +4128,18 @@
 rseError
-(
+, 
 function
@@ -4218,15 +4218,14 @@
   %7D%0A
+
     %7D)
-)
 ;%0A  

Remove filterParseError