// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

function inherits(childCtor, parentCtor) {
  childCtor.prototype.__proto__ = parentCtor.prototype;
};

/**
 * A thin wrapper around shell's 'read' function showing a file name on error.
 */
function readFile(fileName) {
  try {
    return read(fileName);
  } catch (e) {
    print(fileName + ': ' + (e.message || e));
    throw e;
  }
}

/**
 * Parser for dynamic code optimization state.
 */
function parseState(s) {
  switch (s) {
  case "": return Profile.CodeState.COMPILED;
  case "~": return Profile.CodeState.OPTIMIZABLE;
  case "*": return Profile.CodeState.OPTIMIZED;
  }
  throw new Error("unknown code state: " + s);
}


function IcProcessor() {
  var propertyICParser = [parseInt, parseInt, parseInt, null, null, parseInt,
                          null, null, null];
  LogReader.call(this, {
      'code-creation': {
          parsers: [null, parseInt, parseInt, parseInt, parseInt, null, 'var-args'],
          processor: this.processCodeCreation },
      'code-move': { parsers: [parseInt, parseInt],
          processor: this.processCodeMove },
      'code-delete': { parsers: [parseInt],
          processor: this.processCodeDelete },
      'sfi-move': { parsers: [parseInt, parseInt],
          processor: this.processFunctionMove },
      'LoadIC': {
        parsers : propertyICParser,
        processor: this.processPropertyIC.bind(this, "LoadIC") },
      'StoreIC': {
        parsers : propertyICParser,
        processor: this.processPropertyIC.bind(this, "StoreIC") },
      'KeyedLoadIC': {
        parsers : propertyICParser,
        processor: this.processPropertyIC.bind(this, "KeyedLoadIC") },
      'KeyedStoreIC': {
        parsers : propertyICParser,
        processor: this.processPropertyIC.bind(this, "KeyedStoreIC") },
      });
  this.deserializedEntriesNames_ = [];
  this.profile_ = new Profile();

  this.LoadIC = 0;
  this.StoreIC = 0;
  this.KeyedLoadIC = 0;
  this.KeyedStoreIC = 0;
}
inherits(IcProcessor, LogReader);

/**
 * @override
 */
IcProcessor.prototype.printError = function(str) {
  print(str);
};

IcProcessor.prototype.processString = function(string) {
  var end = string.length;
  var current = 0;
  var next = 0;
  var line;
  var i = 0;
  var entry;
  while (current < end) {
    next = string.indexOf("\n", current);
    if (next === -1) break;
    i++;
    line = string.substring(current, next);
    current = next + 1;
    this.processLogLine(line);
  }
}

IcProcessor.prototype.processLogFile = function(fileName) {
  this.collectEntries = true
  this.lastLogFileName_ = fileName;
  var line;
  while (line = readline()) {
    this.processLogLine(line);
  }
  print();
  print("=====================");
  print("Load: " + this.LoadIC);
  print("Store: " + this.StoreIC);
  print("KeyedLoad: " + this.KeyedLoadIC);
  print("KeyedStore: " + this.KeyedStoreIC);
};

IcProcessor.prototype.addEntry = function(entry) {
  this.entries.push(entry);
}

IcProcessor.prototype.processCodeCreation = function(
    type, kind, timestamp, start, size, name, maybe_func) {
  name = this.deserializedEntriesNames_[start] || name;
  if (name.startsWith("onComplete")) {
    console.log(name);
  }
  if (maybe_func.length) {
    var funcAddr = parseInt(maybe_func[0]);
    var state = parseState(maybe_func[1]);
    this.profile_.addFuncCode(type, name, timestamp, start, size, funcAddr, state);
  } else {
    this.profile_.addCode(type, name, timestamp, start, size);
  }
};


IcProcessor.prototype.processCodeMove = function(from, to) {
  this.profile_.moveCode(from, to);
};


IcProcessor.prototype.processCodeDelete = function(start) {
  this.profile_.deleteCode(start);
};


IcProcessor.prototype.processFunctionMove = function(from, to) {
  this.profile_.moveFunc(from, to);
};

IcProcessor.prototype.formatName = function(entry) {
  if (!entry) return "<unknown>"
  var name = entry.func.getName();
  var re = /(.*):[0-9]+:[0-9]+$/;
  var array = re.exec(name);
  if (!array) return name;
  return entry.getState() + array[1];
}

IcProcessor.prototype.processPropertyIC = function (
    type, pc, line, column, old_state, new_state, map, name, modifier,
    slow_reason) {
  this[type]++;
  var entry = this.profile_.findEntry(pc);
  print(type + " (" + old_state + "->" + new_state + modifier + ") at " +
        this.formatName(entry) + ":" + line + ":" + column + " " + name +
        " (map 0x" + map.toString(16) + ")");
}

function padLeft(s, len) {
  s = s.toString();
  if (s.length < len) {
    var padLength = len - s.length;
    if (!(padLength in padLeft)) {
      padLeft[padLength] = new Array(padLength + 1).join(' ');
    }
    s = padLeft[padLength] + s;
  }
  return s;
};


function ArgumentsProcessor(args) {
  this.args_ = args;
  this.result_ = ArgumentsProcessor.DEFAULTS;

  this.argsDispatch_ = {
    '--range': ['range', 'auto,auto',
        'Specify the range limit as [start],[end]'],
    '--source-map': ['sourceMap', null,
        'Specify the source map that should be used for output']
  };
};


ArgumentsProcessor.DEFAULTS = {
  logFileName: 'v8.log',
  range: 'auto,auto',
};


ArgumentsProcessor.prototype.parse = function() {
  while (this.args_.length) {
    var arg = this.args_.shift();
    if (arg.charAt(0) != '-') {
      this.result_.logFileName = arg;
      continue;
    }
    var userValue = null;
    var eqPos = arg.indexOf('=');
    if (eqPos != -1) {
      userValue = arg.substr(eqPos + 1);
      arg = arg.substr(0, eqPos);
    }
    if (arg in this.argsDispatch_) {
      var dispatch = this.argsDispatch_[arg];
      this.result_[dispatch[0]] = userValue == null ? dispatch[1] : userValue;
    } else {
      return false;
    }
  }
  return true;
};


ArgumentsProcessor.prototype.result = function() {
  return this.result_;
};


ArgumentsProcessor.prototype.printUsageAndExit = function() {

  function padRight(s, len) {
    s = s.toString();
    if (s.length < len) {
      s = s + (new Array(len - s.length + 1).join(' '));
    }
    return s;
  }

  print('Cmdline args: [options] [log-file-name]\n' +
        'Default log file name is "' +
        ArgumentsProcessor.DEFAULTS.logFileName + '".\n');
  print('Options:');
  for (var arg in this.argsDispatch_) {
    var synonyms = [arg];
    var dispatch = this.argsDispatch_[arg];
    for (var synArg in this.argsDispatch_) {
      if (arg !== synArg && dispatch === this.argsDispatch_[synArg]) {
        synonyms.push(synArg);
        delete this.argsDispatch_[synArg];
      }
    }
    print('  ' + padRight(synonyms.join(', '), 20) + " " + dispatch[2]);
  }
  quit(2);
};
