const HEAD_HEADER_TEXT    = 'HEAD//our changes';
const ORIGIN_HEADER_TEXT  = 'origin//their changes';
const HEAD_BUTTON_TITLE   = 'Use ours';
const ORIGIN_BUTTON_TITLE = 'Use theirs';
const INTERACTIVE_RESOLVE_MODE = 'interactive';
const EDIT_RESOLVE_MODE = 'edit';
const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE;

class MergeConflictDataProvider {

  getInitialData() {
    // TODO: remove reliance on jQuery and DOM state introspection
    const diffViewType = $.cookie('diff_view');
    const fixedLayout = $('.content-wrapper .container-fluid').hasClass('container-limited');

    return {
      isLoading      : true,
      hasError       : false,
      isParallel     : diffViewType === 'parallel',
      diffViewType   : diffViewType,
      fixedLayout    : fixedLayout,
      isSubmitting   : false,
      conflictsData  : {}
    }
  }


  decorateData(vueInstance, data) {
    this.vueInstance = vueInstance;

    if (data.type === 'error') {
      vueInstance.hasError = true;
      data.errorMessage = data.message;
    }
    else {
      data.shortCommitSha = data.commit_sha.slice(0, 7);
      data.commitMessage  = data.commit_message;

      this.decorateFiles(data);
      this.setParallelLines(data);
      this.setInlineLines(data);
    }

    vueInstance.conflictsData = data;
    vueInstance.isSubmitting = false;

    const conflictsText = this.getConflictsCount() > 1 ? 'conflicts' : 'conflict';
    vueInstance.conflictsData.conflictsText = conflictsText;
  }

  decorateFiles(data) {
    data.files.forEach((file) => {
      file.content = '';
      file.resolutionData = {};
      file.promptDiscardConfirmation = false;
      file.resolveMode = DEFAULT_RESOLVE_MODE;
    });
  }


  setParallelLines(data) {
    data.files.forEach( (file) => {
      file.filePath  = this.getFilePath(file);
      file.iconClass = `fa-${file.blob_icon}`;
      file.blobPath  = file.blob_path;
      file.parallelLines = [];
      const linesObj = { left: [], right: [] };

      file.sections.forEach( (section) => {
        const { conflict, lines, id } = section;

        if (conflict) {
          linesObj.left.push(this.getOriginHeaderLine(id));
          linesObj.right.push(this.getHeadHeaderLine(id));
        }

        lines.forEach( (line) => {
          const { type } = line;

          if (conflict) {
            if (type === 'old') {
              linesObj.left.push(this.getLineForParallelView(line, id, 'conflict'));
            }
            else if (type === 'new') {
              linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true));
            }
          }
          else {
            const lineType = type || 'context';

            linesObj.left.push (this.getLineForParallelView(line, id, lineType));
            linesObj.right.push(this.getLineForParallelView(line, id, lineType, true));
          }
        });

        this.checkLineLengths(linesObj);
      });

      for (let i = 0, len = linesObj.left.length; i < len; i++) {
        file.parallelLines.push([
          linesObj.right[i],
          linesObj.left[i]
        ]);
      }

    });
  }


  checkLineLengths(linesObj) {
    let { left, right } = linesObj;

    if (left.length !== right.length) {
      if (left.length > right.length) {
        const diff = left.length - right.length;
        for (let i = 0; i < diff; i++) {
          right.push({ lineType: 'emptyLine', richText: '' });
        }
      }
      else {
        const diff = right.length - left.length;
        for (let i = 0; i < diff; i++) {
          left.push({ lineType: 'emptyLine', richText: '' });
        }
      }
    }
  }


  setInlineLines(data) {
    data.files.forEach( (file) => {
      file.iconClass   = `fa-${file.blob_icon}`;
      file.blobPath    = file.blob_path;
      file.filePath    = this.getFilePath(file);
      file.inlineLines = []

      file.sections.forEach( (section) => {
        let currentLineType = 'new';
        const { conflict, lines, id } = section;

        if (conflict) {
          file.inlineLines.push(this.getHeadHeaderLine(id));
        }

        lines.forEach( (line) => {
          const { type } = line;

          if ((type === 'new' || type === 'old') && currentLineType !== type) {
            currentLineType = type;
            file.inlineLines.push({ lineType: 'emptyLine', richText: '' });
          }

          this.decorateLineForInlineView(line, id, conflict);
          file.inlineLines.push(line);
        })

        if (conflict) {
          file.inlineLines.push(this.getOriginHeaderLine(id));
        }
      });
    });
  }


  handleSelected(file, sectionId, selection) {
    const vi = this.vueInstance;
    let files = vi.conflictsData.files;

    vi.$set(`conflictsData.files[${files.indexOf(file)}].resolutionData['${sectionId}']`, selection);


    files.forEach( (file) => {
      file.inlineLines.forEach( (line) => {
        if (line.id === sectionId && (line.hasConflict || line.isHeader)) {
          this.markLine(line, selection);
        }
      });

      file.parallelLines.forEach( (lines) => {
        const left         = lines[0];
        const right        = lines[1];
        const hasSameId    = right.id === sectionId || left.id === sectionId;
        const isLeftMatch  = left.hasConflict || left.isHeader;
        const isRightMatch = right.hasConflict || right.isHeader;

        if (hasSameId && (isLeftMatch || isRightMatch)) {
          this.markLine(left, selection);
          this.markLine(right, selection);
        }
      })
    });
  }


  updateViewType(newType) {
    const vi = this.vueInstance;

    if (newType === vi.diffViewType || !(newType === 'parallel' || newType === 'inline')) {
      return;
    }

    vi.diffViewType = newType;
    vi.isParallel   = newType === 'parallel';
    $.cookie('diff_view', newType, {
      path: (gon && gon.relative_url_root) || '/'
    });
    $('.content-wrapper .container-fluid')
      .toggleClass('container-limited', !vi.isParallel && vi.fixedLayout);
  }

  setFileResolveMode(file, mode) {
    const vi = this.vueInstance;

    // Restore Interactive mode when switching to Edit mode
    if (mode === EDIT_RESOLVE_MODE) {
      file.resolutionData = {};

      this.restoreFileLinesState(file);
    }

    file.resolveMode = mode;
  }


  restoreFileLinesState(file) {
    file.inlineLines.forEach((line) => {
      if (line.hasConflict || line.isHeader) {
        line.isSelected = false;
        line.isUnselected = false;
      }
    });

    file.parallelLines.forEach((lines) => {
      const left         = lines[0];
      const right        = lines[1];
      const isLeftMatch  = left.hasConflict || left.isHeader;
      const isRightMatch = right.hasConflict || right.isHeader;

      if (isLeftMatch || isRightMatch) {
        left.isSelected = false;
        left.isUnselected = false;
        right.isSelected = false;
        right.isUnselected = false;
      }
    });
  }


  setPromptConfirmationState(file, state) {
    file.promptDiscardConfirmation = state;
  }


  markLine(line, selection) {
    if (selection === 'head' && line.isHead) {
      line.isSelected   = true;
      line.isUnselected = false;
    }
    else if (selection === 'origin' && line.isOrigin) {
      line.isSelected   = true;
      line.isUnselected = false;
    }
    else {
      line.isSelected   = false;
      line.isUnselected = true;
    }
  }


  getConflictsCount() {
    const files = this.vueInstance.conflictsData.files;
    let count = 0;

    files.forEach((file) => {
      file.sections.forEach((section) => {
        if (section.conflict) {
          count++;
        }
      });
    });

    return count;
  }


  isReadyToCommit() {
    const vi = this.vueInstance;
    const files = this.vueInstance.conflictsData.files;
    const hasCommitMessage = $.trim(this.vueInstance.conflictsData.commitMessage).length;
    let unresolved = 0;

    for (let i = 0, l = files.length; i < l; i++) {
      let file = files[i];

      if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
        let numberConflicts = 0;
        let resolvedConflicts = Object.keys(file.resolutionData).length

        for (let j = 0, k = file.sections.length; j < k; j++) {
          if (file.sections[j].conflict) {
            numberConflicts++;
          }
        }

        if (resolvedConflicts !== numberConflicts) {
          unresolved++;
        }
      } else if (file.resolveMode === EDIT_RESOLVE_MODE) {
        // Unlikely to happen since switching to Edit mode saves content automatically.
        // Checking anyway in case the save strategy changes in the future
        if (!file.content) {
          unresolved++;
          continue;
        }
      }
    }

    return !vi.isSubmitting && hasCommitMessage && !unresolved;
  }


  getCommitButtonText() {
    const initial = 'Commit conflict resolution';
    const inProgress = 'Committing...';
    const vue = this.vueInstance;

    return vue ? vue.isSubmitting ? inProgress : initial : initial;
  }


  decorateLineForInlineView(line, id, conflict) {
    const { type }    = line;
    line.id           = id;
    line.hasConflict  = conflict;
    line.isHead       = type === 'new';
    line.isOrigin     = type === 'old';
    line.hasMatch     = type === 'match';
    line.richText     = line.rich_text;
    line.isSelected   = false;
    line.isUnselected = false;
  }

  getLineForParallelView(line, id, lineType, isHead) {
    const { old_line, new_line, rich_text } = line;
    const hasConflict = lineType === 'conflict';

    return {
      id,
      lineType,
      hasConflict,
      isHead       : hasConflict && isHead,
      isOrigin     : hasConflict && !isHead,
      hasMatch     : lineType === 'match',
      lineNumber   : isHead ? new_line : old_line,
      section      : isHead ? 'head' : 'origin',
      richText     : rich_text,
      isSelected   : false,
      isUnselected : false
    }
  }


  getHeadHeaderLine(id) {
    return {
      id          : id,
      richText    : HEAD_HEADER_TEXT,
      buttonTitle : HEAD_BUTTON_TITLE,
      type        : 'new',
      section     : 'head',
      isHeader    : true,
      isHead      : true,
      isSelected  : false,
      isUnselected: false
    }
  }


  getOriginHeaderLine(id) {
    return {
      id          : id,
      richText    : ORIGIN_HEADER_TEXT,
      buttonTitle : ORIGIN_BUTTON_TITLE,
      type        : 'old',
      section     : 'origin',
      isHeader    : true,
      isOrigin    : true,
      isSelected  : false,
      isUnselected: false
    }
  }


  handleFailedRequest(vueInstance, data) {
    vueInstance.hasError = true;
    vueInstance.conflictsData.errorMessage = 'Something went wrong!';
  }


  getCommitData() {
    let conflictsData = this.vueInstance.conflictsData;
    let commitData = {};

    commitData = {
      commitMessage: conflictsData.commitMessage,
      files: []
    };

    conflictsData.files.forEach((file) => {
      let addFile;

      addFile = {
        old_path: file.old_path,
        new_path: file.new_path
      };

      // Submit only one data for type of editing
      if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
        addFile.sections = file.resolutionData;
      } else if (file.resolveMode === EDIT_RESOLVE_MODE) {
        addFile.content = file.content;
      }

      commitData.files.push(addFile);
    });

    return commitData;
  }


  getFilePath(file) {
    const { old_path, new_path } = file;
    return old_path === new_path ? new_path : `${old_path} → ${new_path}`;
  }
}