// Copyright 2015 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.

"use strict";

class TextView extends View {
  constructor(id, broker, patterns, allowSpanSelection) {
    super(id, broker);
    let view = this;
    view.hide();
    view.textListNode = view.divNode.getElementsByTagName('ul')[0];
    view.fillerSvgElement = view.divElement.append("svg").attr('version','1.1').attr("width", "0");
    view.patterns = patterns;
    view.allowSpanSelection = allowSpanSelection;
    view.nodeToLineMap = [];
    var selectionHandler = {
      clear: function() {
        broker.clear(selectionHandler);
      },
      select: function(items, selected) {
        for (let i of items) {
          if (selected) {
            i.classList.add("selected");
          } else {
            i.classList.remove("selected");
          }
        }
        broker.clear(selectionHandler);
        broker.select(selectionHandler, view.getLocations(items), selected);
      },
      selectionDifference: function(span1, inclusive1, span2, inclusive2) {
        return null;
      },
      brokeredSelect: function(locations, selected) {
        view.selectLocations(locations, selected, true);
      },
      brokeredClear: function() {
        view.selection.clear();
      }
    };
    view.selection = new Selection(selectionHandler);
    broker.addSelectionHandler(selectionHandler);
  }

  setPatterns(patterns) {
    let view = this;
    view.patterns = patterns;
  }

  clearText() {
    let view = this;
    while (view.textListNode.firstChild) {
      view.textListNode.removeChild(view.textListNode.firstChild);
    }
  }

  sameLocation(l1, l2) {
    let view = this;
    if (l1.block_id != undefined && l2.block_id != undefined &&
      l1.block_id == l2.block_id && l1.node_id === undefined) {
      return true;
    }

    if (l1.address != undefined && l1.address == l2.address) {
      return true;
    }

    let node1 = l1.node_id;
    let node2 = l2.node_id;

    if (node1 === undefined || node2 == undefined) {
      if (l1.pos_start === undefined || l2.pos_start == undefined) {
        return false;
      }
      if (l1.pos_start == -1 || l2.pos_start == -1) {
        return false;
      }
      if (l1.pos_start < l2.pos_start) {
        return l1.pos_end > l2.pos_start;
      } {
        return l1.pos_start < l2.pos_end;
      }
    }

    return l1.node_id == l2.node_id;
  }

  selectLocations(locations, selected, makeVisible) {
    let view = this;
    let s = new Set();
    for (let l of locations) {
      for (let i = 0; i < view.textListNode.children.length; ++i) {
        let child = view.textListNode.children[i];
        if (child.location != undefined && view.sameLocation(l, child.location)) {
          s.add(child);
        }
      }
    }
    view.selectCommon(s, selected, makeVisible);
  }

  getLocations(items) {
    let result = [];
    let lastObject = null;
    for (let i of items) {
      if (i.location) {
        result.push(i.location);
      }
    }
    return result;
  }

  createFragment(text, style) {
    let view = this;
    let span = document.createElement("SPAN");
    span.onmousedown = function(e) {
      view.mouseDownSpan(span, e);
    }
    if (style != undefined) {
      span.classList.add(style);
    }
    span.innerHTML = text;
    return span;
  }

  appendFragment(li, fragment) {
    li.appendChild(fragment);
  }

  processLine(line) {
    let view = this;
    let result = [];
    let patternSet = 0;
    while (true) {
      let beforeLine = line;
      for (let pattern of view.patterns[patternSet]) {
        let matches = line.match(pattern[0]);
        if (matches != null) {
          if (matches[0] != '') {
            let style = pattern[1] != null ? pattern[1] : {};
            let text = matches[0];
            if (text != '') {
              let fragment = view.createFragment(matches[0], style.css);
              if (style.link) {
                fragment.classList.add('linkable-text');
                fragment.link = style.link;
              }
              result.push(fragment);
              if (style.location != undefined) {
                let location = style.location(text);
                if (location != undefined) {
                  fragment.location = location;
                }
              }
            }
            line = line.substr(matches[0].length);
          }
          let nextPatternSet = patternSet;
          if (pattern.length > 2) {
            nextPatternSet = pattern[2];
          }
          if (line == "") {
            if (nextPatternSet != -1) {
              throw("illegal parsing state in text-view in patternSet" + patternSet);
            }
            return result;
          }
          patternSet = nextPatternSet;
          break;
        }
      }
      if (beforeLine == line) {
        throw("input not consumed in text-view in patternSet" + patternSet);
      }
    }
  }

  select(s, selected, makeVisible) {
    let view = this;
    view.selection.clear();
    view.selectCommon(s, selected, makeVisible);
  }

  selectCommon(s, selected, makeVisible) {
    let view = this;
    let firstSelect = makeVisible && view.selection.isEmpty();
    if ((typeof s) === 'function') {
      for (let i = 0; i < view.textListNode.children.length; ++i) {
        let child = view.textListNode.children[i];
        if (child.location && s(child.location)) {
          if (firstSelect) {
            makeContainerPosVisible(view.parentNode, child.offsetTop);
            firstSelect = false;
          }
          view.selection.select(child, selected);
        }
      }
    } else if (typeof s[Symbol.iterator] === 'function') {
      if (firstSelect) {
        for (let i of s) {
          makeContainerPosVisible(view.parentNode, i.offsetTop);
          break;
        }
      }
      view.selection.select(s, selected);
    } else {
      if (firstSelect) {
        makeContainerPosVisible(view.parentNode, s.offsetTop);
      }
      view.selection.select(s, selected);
    }
  }

  mouseDownLine(li, e) {
    let view = this;
    e.stopPropagation();
    if (!e.shiftKey) {
      view.selection.clear();
    }
    if (li.location != undefined) {
      view.selectLocations([li.location], true, false);
    }
  }

  mouseDownSpan(span, e) {
    let view = this;
    if (view.allowSpanSelection) {
      e.stopPropagation();
      if (!e.shiftKey) {
        view.selection.clear();
      }
      select(li, true);
    } else if (span.link) {
      span.link(span.textContent);
      e.stopPropagation();
    }
  }

  processText(text) {
    let view = this;
    let textLines = text.split(/[\n]/);
    let lineNo = 0;
    for (let line of textLines) {
      let li = document.createElement("LI");
      li.onmousedown = function(e) {
        view.mouseDownLine(li, e);
      }
      li.className = "nolinenums";
      li.lineNo = lineNo++;
      let fragments = view.processLine(line);
      for (let fragment of fragments) {
        view.appendFragment(li, fragment);
      }
      let lineLocation = view.lineLocation(li);
      if (lineLocation != undefined) {
        li.location = lineLocation;
      }
      view.textListNode.appendChild(li);
    }
  }

  initializeContent(data, rememberedSelection) {
    let view = this;
    view.selection.clear();
    view.clearText();
    view.processText(data);
    var fillerSize = document.documentElement.clientHeight -
        view.textListNode.clientHeight;
    if (fillerSize < 0) {
      fillerSize = 0;
    }
    view.fillerSvgElement.attr("height", fillerSize);
  }

  deleteContent() {
  }

  isScrollable() {
    return true;
  }

  detachSelection() {
    return null;
  }

  lineLocation(li) {
    let view = this;
    for (let i = 0; i < li.children.length; ++i) {
      let fragment = li.children[i];
      if (fragment.location != undefined && !view.allowSpanSelection) {
        return fragment.location;
      }
    }
  }
}