import { CodeEditor } from '@jupyterlab/codeeditor';
import { expect } from 'chai';

import { ISourcePosition, IVirtualPosition } from '../positioning';
import { foreign_code_extractors } from '../transclusions/ipython-rpy2/extractors';

import { BrowserConsole } from './console';
import { VirtualDocument, is_within_range } from './document';

import Mock = jest.Mock;

let R_LINE_MAGICS = `%R df = data.frame()
print("df created")
%R ggplot(df)
print("plotted")
`;

describe('is_within_range', () => {
  let line_range: CodeEditor.IRange = {
    start: { line: 1, column: 0 },
    end: { line: 1, column: 10 }
  };
  let long_range: CodeEditor.IRange = {
    start: { line: 0, column: 3 },
    end: { line: 1, column: 0 }
  };
  it('recognizes positions within range in a single-line case', () => {
    expect(is_within_range({ line: 1, column: 0 }, line_range)).to.equal(true);
    expect(is_within_range({ line: 1, column: 5 }, line_range)).to.equal(true);
    expect(is_within_range({ line: 1, column: 10 }, line_range)).to.equal(true);
  });

  it('recognizes positions outside of range in a single-line case', () => {
    expect(is_within_range({ line: 0, column: 0 }, line_range)).to.equal(false);
    expect(is_within_range({ line: 2, column: 0 }, line_range)).to.equal(false);
  });

  it('recognizes positions within range in multi-line case', () => {
    expect(is_within_range({ line: 0, column: 3 }, long_range)).to.equal(true);
    expect(is_within_range({ line: 0, column: 5 }, long_range)).to.equal(true);
    expect(is_within_range({ line: 1, column: 0 }, long_range)).to.equal(true);
  });

  it('recognizes positions outside of range in multi-line case', () => {
    expect(is_within_range({ line: 0, column: 0 }, long_range)).to.equal(false);
    expect(is_within_range({ line: 0, column: 1 }, long_range)).to.equal(false);
    expect(is_within_range({ line: 0, column: 2 }, long_range)).to.equal(false);
    expect(is_within_range({ line: 1, column: 1 }, long_range)).to.equal(false);
  });
});

describe('VirtualDocument', () => {
  let document: VirtualDocument;
  beforeEach(() => {
    document = new VirtualDocument({
      language: 'python',
      path: 'test.ipynb',
      overrides_registry: {},
      foreign_code_extractors: foreign_code_extractors,
      standalone: false,
      file_extension: 'py',
      has_lsp_supported_file: false,
      console: new BrowserConsole()
    });
  });

  describe('#dispose', () => {
    it('disposes, but does not break methods which can be called from async callbacks', () => {
      expect(document.isDisposed).to.equal(false);
      // appending code block here should work fine
      document.append_code_block({
        value: 'code',
        ce_editor: {} as CodeEditor.IEditor
      });
      document.dispose();
      expect(document.isDisposed).to.equal(true);
      // mock console.warn
      console.warn = jest.fn();
      // this one should not raise, but just warn
      document.append_code_block({
        value: 'code',
        ce_editor: {} as CodeEditor.IEditor
      });
      expect((console.warn as Mock).mock.calls[0][0]).to.equal(
        'Cannot append code block: document disposed'
      );
    });
  });

  describe('#extract_foreign_code', () => {
    it('joins non-standalone fragments together', () => {
      let { cell_code_kept, foreign_document_map } =
        document.extract_foreign_code(
          { value: R_LINE_MAGICS, ce_editor: null as any },
          {
            line: 0,
            column: 0
          }
        );

      // note R cell lines are kept in code (keep_in_host=true)
      expect(cell_code_kept).to.equal(R_LINE_MAGICS);
      expect(foreign_document_map.size).to.equal(2);

      let { virtual_document: r_document } = foreign_document_map.get(
        foreign_document_map.keys().next().value
      )!;
      expect(r_document.language).to.equal('r');
      expect(r_document.value).to.equal('df = data.frame()\n\n\nggplot(df)\n');
    });
  });

  afterEach(() => {
    document.clear();
  });

  let init_document_with_Python_and_R = () => {
    let ce_editor_for_cell_1 = {} as CodeEditor.IEditor;
    let ce_editor_for_cell_2 = {} as CodeEditor.IEditor;
    let ce_editor_for_cell_3 = {} as CodeEditor.IEditor;
    let ce_editor_for_cell_4 = {} as CodeEditor.IEditor;
    // first block
    document.append_code_block({
      value: 'test line in Python 1\n%R 1st test line in R line magic 1',
      ce_editor: ce_editor_for_cell_1
    });
    // second block
    document.append_code_block({
      value: 'test line in Python 2\n%R 1st test line in R line magic 2',
      ce_editor: ce_editor_for_cell_2
    });
    // third block
    document.append_code_block({
      value:
        'test line in Python 3\n%R -i imported_variable 1st test line in R line magic 3',
      ce_editor: ce_editor_for_cell_2
    });
    // fourth block
    document.append_code_block({
      value: '%%R\n1st test line in R cell magic 1',
      ce_editor: ce_editor_for_cell_3
    });
    // fifth block
    document.append_code_block({
      value: '%%R -i imported_variable\n1st test line in R cell magic 2',
      ce_editor: ce_editor_for_cell_4
    });
  };

  describe('#transform_virtual_to_editor', () => {
    it('transforms positions for the top level document', () => {
      init_document_with_Python_and_R();
      // The first (Python) line in the first block
      let editor_position = document.transform_virtual_to_editor({
        line: 0,
        ch: 0
      } as IVirtualPosition)!;
      expect(editor_position.line).to.equal(0);
      expect(editor_position.ch).to.equal(0);

      // The first (Python) line in the second block
      editor_position = document.transform_virtual_to_editor({
        line: 4,
        ch: 0
      } as IVirtualPosition)!;
      expect(editor_position.line).to.equal(0);
      expect(editor_position.ch).to.equal(0);
    });

    it('transforms positions for the nested foreign documents', () => {
      init_document_with_Python_and_R();
      let foreign_document = document.document_at_source_position({
        line: 1,
        ch: 3
      } as ISourcePosition);
      expect(foreign_document).to.not.equal(document);
      expect(foreign_document.value).to.equal(
        '1st test line in R line magic 1\n\n\n' +
          '1st test line in R line magic 2\n\n\n' +
          'imported_variable <- data.frame(); 1st test line in R line magic 3\n\n\n' +
          // 23456789012345678901234567890123456 - 's' is 36th
          '1st test line in R cell magic 1\n\n\n' +
          'imported_variable <- data.frame(); 1st test line in R cell magic 2\n'
        // 0123456789012345678901234567890123456 - 's' is 36th
      );

      // The first R line (in source); second in the first block;
      // targeting "s" in "1st", "1st" in "1st test line in R line magic" (first virtual line == line 0)
      let virtual_r_1_1 = {
        line: 0,
        ch: 1
      } as IVirtualPosition;

      // For future reference, the code below would be wrong:
      // let source_position = foreign_document.transform_virtual_to_source(virtual_r_1_1);
      // expect(source_position.line).to.equal(1);
      // expect(source_position.ch).to.equal(4);
      // because it checks R source position, rather than checking root source positions.

      let editor_position =
        foreign_document.transform_virtual_to_editor(virtual_r_1_1)!;
      expect(editor_position.line).to.equal(1);
      expect(editor_position.ch).to.equal(4);

      // The second R line (in source), second in the second block
      // targeting 1 in "1st test line in R line magic 2" (4th virtual line == line 3)
      editor_position = foreign_document.transform_virtual_to_editor({
        line: 3,
        ch: 0
      } as IVirtualPosition)!;
      // 0th editor line is 'test line in Python 2\n'
      expect(editor_position.line).to.equal(1);
      // 1st editor lines is '%R 1st test line in R line magic 2'
      //                      0123 - 3rd character
      expect(editor_position.ch).to.equal(3);

      // The third R line (in source), second in the third block;
      // targeting "s" in "1st" in "1st test line in R line magic 3" (7th virtual line == line 6)
      editor_position = foreign_document.transform_virtual_to_editor({
        line: 6,
        ch: 36
      } as IVirtualPosition)!;
      // 0th editor line is 'test line in Python 3\n'
      expect(editor_position.line).to.equal(1);
      // 1st editor line is '%R -i imported_variable 1st test line in R line magic 3'
      //                     01234567890123456789012345 - 25th character
      expect(editor_position.ch).to.equal(25);

      // The fifth R line (in source), second in the fifth block;
      // targeting "s" in "1st" in "1st test line in R cell magic 2" (13th virtual lines == line 12)
      editor_position = foreign_document.transform_virtual_to_editor({
        line: 12,
        ch: 36
      } as IVirtualPosition)!;
      // 0th editor line is '%%R -i imported_variable\n'
      expect(editor_position.line).to.equal(1);
      // 1st editor line is '1st test line in R cell magic 2'
      //                     01
      expect(editor_position.ch).to.equal(1);
    });
  });
});
