import actions
import logger

import testsamples
import test_engine
from test_engine import Table, Column

log = logger.Logger(__name__, logger.INFO)

class TestDocModel(test_engine.EngineTestCase):

  def test_meta_tables(self):
    """
    Test changes to records accessed via lookup.
    """
    self.load_sample(testsamples.sample_students)
    self.assertPartialData("_grist_Tables", ["id", "columns"], [
      [1,   [1,2,4,5,6]],
      [2,   [10,12]],
      [3,   [21]],
    ])

    # Test that adding a column produces a change to 'columns' without emitting an action.
    out_actions = self.add_column('Students', 'test', type='Text', isFormula=False)
    self.assertPartialData("_grist_Tables", ["id", "columns"], [
      [1,   [1,2,4,5,6,22]],
      [2,   [10,12]],
      [3,   [21]],
    ])
    self.assertPartialOutActions(out_actions, {
      "calc": [],
      "stored": [
        ["AddColumn", "Students", "test",
         {"formula": "", "isFormula": False, "type": "Text"}
        ],
        ["AddRecord", "_grist_Tables_column", 22,
         {"colId": "test", "formula": "", "isFormula": False, "label": "test",
          "parentId": 1, "parentPos": 6.0, "type": "Text", "widgetOptions": ""}
        ],
      ],
      "undo": [
        ["RemoveColumn", "Students", "test"],
        ["RemoveRecord", "_grist_Tables_column", 22],
      ]
    })

    # Undo the AddColumn action. Check that actions are in correct order, and still produce undos.
    out_actions = self.apply_user_action(
      ['ApplyUndoActions', [actions.get_action_repr(a) for a in out_actions.undo]])
    self.assertPartialOutActions(out_actions, {
      "calc": [],
      "stored": [
        ["RemoveRecord", "_grist_Tables_column", 22],
        ["RemoveColumn", "Students", "test"],
      ],
      "undo": [
        ["AddRecord", "_grist_Tables_column", 22, {"colId": "test", "label": "test",
         "parentId": 1, "parentPos": 6.0, "type": "Text"}],
        ["AddColumn", "Students", "test", {"formula": "", "isFormula": False, "type": "Text"}],
      ]
    })

    # Test that when we add a table, .column is set correctly.
    out_actions = self.apply_user_action(['AddTable', 'Test2', [
      {'id': 'A', 'type': 'Text'},
      {'id': 'B', 'type': 'Numeric'},
      {'id': 'C', 'type': 'Numeric', 'formula': 'len($A)', 'isFormula': True}
    ]])
    self.assertPartialData("_grist_Tables", ["id", "columns"], [
      [1,   [1,2,4,5,6]],
      [2,   [10,12]],
      [3,   [21]],
      [4,   [22,23,24,25]],
    ])
    self.assertPartialData("_grist_Tables_column", ["id", "colId", "parentId"], [
      [1, "firstName",    1],
      [2, "lastName",     1],
      [4, "schoolName",   1],
      [5, "schoolIds",    1],
      [6, "schoolCities", 1],
      [10, "name",        2],
      [12, "address",     2],
      [21, "city",        3],
      # Newly added columns:
      [22,  'manualSort', 4],
      [23,  'A',          4],
      [24,  'B',          4],
      [25,  'C',          4],
    ])

  def test_add_column_position(self):
    self.load_sample(testsamples.sample_students)

    # Client may send AddColumn actions with fractional positions. Test that it works.
    # TODO: this should probably use parentPos in the future and be done via metadata AddRecord.
    out_actions = self.add_column('Students', 'test', type='Text', _position=2.75)
    self.assertPartialData("_grist_Tables", ["id", "columns"], [
      [1,   [1,2,22,4,5,6]],
      [2,   [10,12]],
      [3,   [21]],
    ])

    out_actions = self.add_column('Students', None, type='Text', _position=6)
    self.assertPartialData("_grist_Tables", ["id", "columns"], [
      [1,   [1,2,22,4,5,6,23]],
      [2,   [10,12]],
      [3,   [21]],
    ])
    self.assertPartialData("_grist_Tables_column", ["id", "colId", "parentId"], [
      [1, "firstName",    1],
      [2, "lastName",     1],
      [4, "schoolName",   1],
      [5, "schoolIds",    1],
      [6, "schoolCities", 1],
      [10, "name",        2],
      [12, "address",     2],
      [21, "city",        3],
      [22, "test",        1],
      [23, "A",           1],
    ])

  def assertRecordSet(self, record_set, expected_row_ids):
    self.assertEqual(list(record_set.id), expected_row_ids)

  def test_lookup_recompute(self):
    self.load_sample(testsamples.sample_students)
    self.apply_user_action(['AddTable', 'Test2', [
      {'id': 'A', 'type': 'Text'},
      {'id': 'B', 'type': 'Numeric'},
    ]])
    self.apply_user_action(['AddTable', 'Test3', [
      {'id': 'A', 'type': 'Text'},
      {'id': 'B', 'type': 'Numeric'},
    ]])
    self.apply_user_action(['AddViewSection', 'Section2', 'record', 1, 'Test2'])
    self.apply_user_action(['AddViewSection', 'Section3', 'record', 1, 'Test3'])
    self.assertPartialData('_grist_Views', ["id"], [
      [1],
      [2],
    ])
    self.assertPartialData('_grist_Views_section', ["id", "parentId", "tableRef"], [
      [1, 1, 4],
      [2, 2, 5],
      [3, 1, 4],
      [4, 1, 5],
    ])
    self.assertPartialData('_grist_Views_section_field', ["id", "parentId", "parentPos"], [
      [1, 1, 1.0],
      [2, 1, 2.0],
      [3, 2, 3.0],
      [4, 2, 4.0],
      [5, 3, 5.0],
      [6, 3, 6.0],
      [7, 4, 7.0],
      [8, 4, 8.0],
    ])

    table = self.engine.docmodel.tables.lookupOne(tableId='Test2')
    self.assertRecordSet(table.viewSections, [1,3])
    self.assertRecordSet(list(table.viewSections)[0].fields, [1,2])
    self.assertRecordSet(list(table.viewSections)[1].fields, [5,6])
    view = self.engine.docmodel.views.lookupOne(id=1)
    self.assertRecordSet(view.viewSections, [1,3,4])

    self.engine.docmodel.remove(f for vs in table.viewSections for f in vs.fields)
    self.engine.docmodel.remove(table.viewSections)
    self.assertRecordSet(view.viewSections, [4])


  def test_modifications(self):
    # Test the add/remove/update methods of DocModel.
    self.load_sample(testsamples.sample_students)
    table = self.engine.docmodel.get_table('Students')
    records = table.lookupRecords(lastName='Bush')
    self.assertEqual([r.id for r in records], [2, 4])
    self.assertEqual([r.schoolName for r in records], ["Yale", "Yale"])
    self.assertEqual([r.firstName for r in records], ["George W", "George H"])

    # Test the update() method.
    self.engine.docmodel.update(records, schoolName="Test", firstName=["george w", "george h"])
    self.assertEqual([r.schoolName for r in records], ["Test", "Test"])
    self.assertEqual([r.firstName for r in records], ["george w", "george h"])

    # Test the remove() method.
    self.engine.docmodel.remove(records)
    records = table.lookupRecords(lastName='Bush')
    self.assertEqual(list(records), [])
    self.assertTableData("Students", cols="subset", data=[
        ["id","firstName","lastName", "schoolName" ],
        [1,   "Barack",   "Obama",    "Columbia"   ],
        [3,   "Bill",     "Clinton",  "Columbia"   ],
        [5,   "Ronald",   "Reagan",   "Eureka"     ],
        [6,   "Gerald",   "Ford",     "Yale"       ]])

    # Test the add() method.
    self.engine.docmodel.add(table, schoolName="Foo", firstName=["X", "Y"])
    self.assertTableData("Students", cols="subset", data=[
        ["id","firstName","lastName", "schoolName" ],
        [1,   "Barack",   "Obama",    "Columbia"   ],
        [3,   "Bill",     "Clinton",  "Columbia"   ],
        [5,   "Ronald",   "Reagan",   "Eureka"     ],
        [6,   "Gerald",   "Ford",     "Yale"       ],
        [7,   "X",        "",         "Foo"        ],
        [8,   "Y",        "",         "Foo"        ],
    ])

  def test_inserts(self):
    # Test the insert() method. We do this on the columns metadata table, so that we can sort by
    # a PositionNumber column.
    self.load_sample(testsamples.sample_students)
    student_columns = self.engine.docmodel.tables.lookupOne(tableId='Students').columns
    school_columns = self.engine.docmodel.tables.lookupOne(tableId='Schools').columns

    # Should go at the end of the Students table.
    cols = self.engine.docmodel.insert(student_columns, None, colId=["a", "b"], type="Text")
    # Should go at the start of the Schools table.
    self.engine.docmodel.insert_after(school_columns, None, colId="foo", type="Int")
    # Should go before the new "a", "b" columns of the Students table.
    self.engine.docmodel.insert(student_columns, cols[0].parentPos, colId="bar", type="Date")

    # Verify that the right columns were added to the right tables. This doesn't check positions.
    self.assertTables([
      Table(1, "Students", 0, 0, columns=[
        Column(1, "firstName",    "Text",  False, "", 0),
        Column(2, "lastName",     "Text",  False, "", 0),
        Column(4, "schoolName",   "Text",  False, "", 0),
        Column(5, "schoolIds",    "Text",  True,
               "':'.join(str(id) for id in Schools.lookupRecords(name=$schoolName).id)", 0),
        Column(6, "schoolCities", "Text",  True,
               "':'.join(r.address.city for r in Schools.lookupRecords(name=$schoolName))", 0),
        Column(22, "a",           "Text", False, "", 0),
        Column(23, "b",           "Text", False, "", 0),
        Column(25, "bar",         "Date", False, "", 0),
      ]),
      Table(2, "Schools", 0, 0, columns=[
        Column(10, "name",        "Text", False, "", 0),
        Column(12, "address",     "Ref:Address",False, "", 0),
        Column(24, "foo",         "Int", False, "", 0),
      ]),
      Table(3, "Address", 0, 0, columns=[
        Column(21, "city",        "Text", False, "", 0),
      ])
    ])

    # Verify that positions are set such that the order is what we asked for.
    student_columns = self.engine.docmodel.tables.lookupOne(tableId='Students').columns
    self.assertEqual(list(map(int, student_columns)), [1,2,4,5,6,25,22,23])
    school_columns = self.engine.docmodel.tables.lookupOne(tableId='Schools').columns
    self.assertEqual(list(map(int, school_columns)), [24,10,12])
