const aUniqueVerticalStringNotFoundInData = '___UNIQUE_VERTICAL___';
const aUniqueCommaStringNotFoundInData = '___UNIQUE_COMMA___';
const segmentSeparateChar = '|';
const methodAndArgsSeparateChar = ':';
const argsSeparateChar = ',';

const md5 = require('md5');
const sha = require('sha.js');
const Base64 = require('js-base64').Base64;

export const funcFilters = {
  md5: function (str) {
    return md5(str);
  },

  sha: function (str, arg) {
    return sha(arg)
      .update(str)
      .digest('hex');
  },

  /**
   * type: sha1 sha224 sha256 sha384 sha512
   */
  sha1: function (str) {
    return sha('sha1')
      .update(str)
      .digest('hex');
  },

  sha224: function (str) {
    return sha('sha224')
      .update(str)
      .digest('hex');
  },

  sha256: function (str) {
    return sha('sha256')
      .update(str)
      .digest('hex');
  },

  sha384: function (str) {
    return sha('sha384')
      .update(str)
      .digest('hex');
  },

  sha512: function (str) {
    return sha('sha512')
      .update(str)
      .digest('hex');
  },

  base64: function (str) {
    return Base64.encode(str);
  },

  unbase64: function (str) {
    return Base64.decode(str);
  },

  substr: function (str, ...args) {
    return str.substr(...args);
  },

  concat: function (str, ...args) {
    args.forEach(item => {
      str += item;
    });
    return str;
  },

  lconcat: function (str, ...args) {
    args.forEach(item => {
      str = item + str;
    });
    return str;
  },

  lower: function (str) {
    return str.toLowerCase();
  },

  upper: function (str) {
    return str.toUpperCase();
  },

  length: function (str) {
    return str.length;
  },

  number: function (str) {
    return !isNaN(str) ? +str : str;
  }
};

let handleValue = function (str) {
  return str;
};

const _handleValue = function (str) {
  if (str[0] === str[str.length - 1] && (str[0] === '"' || str[0] === "'")) {
    str = str.substr(1, str.length - 2);
  }
  return handleValue(
    str
      .replace(new RegExp(aUniqueVerticalStringNotFoundInData, 'g'), segmentSeparateChar)
      .replace(new RegExp(aUniqueCommaStringNotFoundInData, 'g'), argsSeparateChar)
  );
};

class PowerString {
  constructor(str) {
    this._string = str;
  }

  toString() {
    return this._string;
  }
}

function addMethod(method, fn) {
  PowerString.prototype[method] = function (...args) {
    args.unshift(this._string + '');
    this._string = fn.apply(this, args);
    return this;
  };
}

function importMethods(handles) {
  for (let method in handles) {
    addMethod(method, handles[method]);
  }
}

importMethods(funcFilters);

function handleOriginStr(str, handleValueFn) {
  if (!str) return str;
  if (typeof handleValueFn === 'function') {
    handleValue = handleValueFn;
  }
  str = str
    .replace('\\' + segmentSeparateChar, aUniqueVerticalStringNotFoundInData)
    .replace('\\' + argsSeparateChar, aUniqueCommaStringNotFoundInData)
    .split(segmentSeparateChar)
    .map(handleSegment)
    .reduce(execute, null)
    .toString();
  return str;
}

function execute(str, curItem, index) {
  if (index === 0) {
    return new PowerString(curItem);
  }
  return str[curItem.method].apply(str, curItem.args);
}

function handleSegment(str, index) {
  str = str.trim();
  if (index === 0) {
    return _handleValue(str);
  }

  let method,
    args = [];
  if (str.indexOf(methodAndArgsSeparateChar) > 0) {
    str = str.split(methodAndArgsSeparateChar);
    method = str[0].trim();
    args = str[1].split(argsSeparateChar).map(item => _handleValue(item.trim()));
  } else {
    method = str;
  }
  if (typeof funcFilters[method] !== 'function') {
    throw new Error(`This method name(${method}) is not exist.`);
  }

  return {
    method,
    args
  };
}
