const DIR = "/tmp/inheritance/"
var inh = eval(read_file(DIR + "inheritance.js"))
var files = undefined
var types = undefined
const NAME = 0
const CFILE = 1
const CLINE = 2
var classPositions = []
var classCache = {}
// makes looking up children cheap
//indexed by parent pos in classPositions-> array of child pos in classPositions
var parent2children = []

for (var i = 0;i < inh.length;) {
  classPositions.push(i)
  //point at first base, or next class
  for (i += 3; i != inh.length; i++) {
    parent = inh[i]
    if (typeof parent == "string") break
    var derived = parent2children[parent]
    if (!derived) {
      parent2children[parent] = derived = []
    }
    derived.push(classPositions.length - 1)
  }
}

function getType(i) {
  if (!types)
    types = eval(read_file(DIR + "types.js"))
  return types[i]
}

function getCachedClass(nameOrPos) {
  var c = classCache[nameOrPos]
  if (!c) {
    // constructor saves stuff in cache
    c = new Class(nameOrPos)
  } else {
    //print("Getting " + c.name + " from cache")
  }
  return c
}

function Class(nameOrPos) {
  if (typeof nameOrPos == "string") {
    // must've been passed a name
    this.i = inh.indexOf(nameOrPos)
    var pos
    for (pos = 0; pos < classPositions.length;pos++)
      if (inh[classPositions[pos] + NAME] == nameOrPos)
        break
    if (pos == classPositions.length) throw "Can't find '" + nameOrPos + "' in the class hierarchy";
    this.i = pos
  } else {
    // so we were passed an int
    this.i = nameOrPos
  }
  // this.i is our position in the classPositions array
  // classes refer to each other using that position
  this.offset = classPositions[this.i];
  this.name = inh[this.offset+NAME]
  classCache[this.i] = this
  classCache[this.name] = this
}

Class.prototype.getMembers = function (name, includeInherited, ls) {
  if (!this.members) {
    var filename = DIR + ["member", inh[this.offset+NAME], inh[this.offset+CFILE], inh[this.offset+CLINE]].join("_") + ".js"
    var str = read_file(filename)
    this.members = typeof str != "undefined" 
      ? eval(str).map(function (m) {return new Member(m)}) 
      : []
    if (typeof str == "undefined") {
      print(this.getLoc()+ ": " + this.name + " has no class members.")
    }
    this.members.sort(function (a, b) {return a.compareTo(b)})
  }
  if (!ls)
    ls = []
  var i = 0
  // keep ls sorted
  for each (var m in this.members) {
    // if given a name
    if (name && m.name != name) continue
    // default to 1 -> insert
    var cmp = 1
    while (i < ls.length) {
      cmp = ls[i].compareTo(m)
      if (cmp >= 0)
        break
      i++
    }
    //ls already has the element, nothing to do, but increment the index
    if (cmp == 0) {
    } else {
      //insert element
      ls.splice(i, 0, m);
    }
    // next element is going to follow one just inserted
    i++;
  }
  if (includeInherited)
    for each (var base in this.getBases()) {
      base.getMembers(name, includeInherited, ls)
    }
  return ls
}
Class.prototype.getMember = function (member, includeInherited) {
  for each (var m in this.getMembers(member.name, includeInherited)) {
    if (member.compareTo(m) == 0)
      return m
  }
}

Class.prototype.getBases = function () {
  var bases = []
  for(var i = this.offset + CLINE + 1; typeof inh[i] != "string" && i != inh.length; i++) 
    bases.push(inh[i])
  return bases.map(function (index) {return getCachedClass(index)})
}

Class.prototype.getParentWithMember = function (member) {
  if (!member.isVirtual) return this

  var bases = this.getBases().map(
    function (base) {
      return base.getParentWithMember(member)
    }).filter(function (x) {return x})
  if (bases.length == 1) {
    return bases[0]
  }
  // perhaps This is the class the implement this member?
  else if (bases.length == 0) { 
    for each (var m in this.getMembers(member.name)) {
      if (m.compareTo(member) == 0)
        return this
    }
  } else {
    print (this.name + ": more than 1 parent seem to implement "+member.name);
  }
  return undefined
}

Class.prototype.getChildren = function (ls) {
  if (!ls)
    ls = []
  var children = parent2children[this.i]
  for each (var child in children) {
    var c = getCachedClass(child)
    ls.push(c);
    c.getChildren(ls)
  }
  return ls
}

Class.prototype.getLoc = function () {
  if (!files)
    files = eval(read_file(DIR + "files.js"))
  var index = inh[this.offset + CFILE]
  return files[index] + ":" + inh[this.offset + CLINE]
}

Class.prototype.isXPIDL = function () {
  return this.getLoc().indexOf("_xpidlgen/") != -1
}

Class.prototype.compareTo = function (b) {
  if (this.name < b.name)
    return -1
  else if (this.name > b.name)
    return 1
  return 0
}

Class.prototype.isPureVirtual = function () {
  var members = this.getMembers()
  var methods = members.filter(function (x) {return x.isMethod()})
  // not pure virtual is there are data members and no pure virtual ones
  if (members.length && !methods.length) 
    return false
  return methods.some(function (x) {return x.isPureVirtual()})
}

function Member(a) {
  const TYPE = 1
  const PARAMS = 3
  const VIRTUAL = 4
  this.name = a[NAME]
  this.type = getType(a[TYPE])
  if (a.length > PARAMS) {
    this.params = a[PARAMS].map(function (p) {return new Parameter(p)})
    if (a.length > VIRTUAL)
      this.isVirtual = a[VIRTUAL]
  } 
}

Member.prototype.isPureVirtual = function () {
  return this.isVirtual == 2
}

Member.prototype.isMethod = function() {
  return !!this.params
}

// isVirtual & name are ignored on purpose
Member.prototype.compareTo = function (b) {
  if (this.name < b.name)
    return -1
  else if (this.name > b.name)
    return 1
  else if (this.type < b.type)
    return -1
  else if (this.type > b.type)
    return 1
  else if (this.params) {
    if (!b.params)
      return 1
    var len = this.params.length
    var diff = this.params.length - b.params.length
    if (diff != 0)
      return diff
    for(var i = 0;i < len;i++) {
      var x = this.params[i].type
      var y = b.params[i].type
      if (x == y) continue
      if (x < y)
        return -1
      return 1
    }
  } else if (!this.params && b.params)
    return -1
  return 0
}

Member.prototype.toString = function () {
  var ret = this.type + " " + this.name
  if (this.isMethod())
    ret += "(" + this.params.join(", ") + ")"
  return ret
}

function Parameter(p) {
  this.name = p[0]
  this.type = getType(p[1])
}

Parameter.prototype.toString = function () {
  return this.type + " " + this.name
}
