import logger

import testutil
import test_engine
from test_engine import Table, Column, View, Section, Field

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

class TestTableActions(test_engine.EngineTestCase):

  address_table_data = [
    ["id",  "city",     "state", "amount" ],
    [ 21,   "New York", "NY"   , 1.       ],
    [ 22,   "Albany",   "NY"   , 2.       ],
    [ 23,   "Seattle",  "WA"   , 3.       ],
    [ 24,   "Chicago",  "IL"   , 4.       ],
    [ 25,   "Bedford",  "MA"   , 5.       ],
    [ 26,   "New York", "NY"   , 6.       ],
    [ 27,   "Buffalo",  "NY"   , 7.       ],
    [ 28,   "Bedford",  "NY"   , 8.       ],
    [ 29,   "Boston",   "MA"   , 9.       ],
    [ 30,   "Yonkers",  "NY"   , 10.      ],
    [ 31,   "New York", "NY"   , 11.      ],
  ]

  people_table_data = [
    ["id",  "name",   "address" ],
    [ 1,    "Alice",  22        ],
    [ 2,    "Bob",    25        ],
    [ 3,    "Carol",  27        ],
  ]

  def init_sample_data(self):
    # Add a couple of tables, including references.
    self.apply_user_action(["AddTable", "Address", [
      {"id": "city",    "type": "Text"},
      {"id": "state",   "type": "Text"},
      {"id": "amount",  "type": "Numeric"},
    ]])
    self.apply_user_action(["AddTable", "People", [
      {"id": "name",    "type": "Text"},
      {"id": "address", "type": "Ref:Address"},
      {"id": "city",    "type": "Any", "formula": "$address.city" }
    ]])

    # Populate some data.
    d = testutil.table_data_from_rows("Address", self.address_table_data[0],
                                      self.address_table_data[1:])
    self.apply_user_action(["BulkAddRecord", "Address", d.row_ids, d.columns])

    d = testutil.table_data_from_rows("People", self.people_table_data[0],
                                      self.people_table_data[1:])
    self.apply_user_action(["BulkAddRecord", "People", d.row_ids, d.columns])

    # Add a view with several sections, including a summary table.
    self.apply_user_action(["CreateViewSection", 1, 0, 'record', None])
    self.apply_user_action(["CreateViewSection", 1, 3, 'record', [3]])
    self.apply_user_action(["CreateViewSection", 2, 3, 'record', None])

    # Verify the new structure of tables and views.
    self.assertTables([
      Table(1, "Address", primaryViewId=1, summarySourceTable=0, columns=[
        Column(1, "manualSort", "ManualSortPos", False, "", 0),
        Column(2, "city",       "Text", False, "", 0),
        Column(3, "state",      "Text", False, "", 0),
        Column(4, "amount",     "Numeric", False, "", 0),
      ]),
      Table(2, "People", primaryViewId=2, summarySourceTable=0, columns=[
        Column(5, "manualSort", "ManualSortPos", False, "", 0),
        Column(6, "name",       "Text",         False, "", 0),
        Column(7, "address",    "Ref:Address",  False, "", 0),
        Column(8, "city",       "Any", True, "$address.city", 0),
      ]),
      Table(3, "GristSummary_7_Address", 0, 1, columns=[
        Column(9, "state", "Text", False, "", summarySourceCol=3),
        Column(10, "group", "RefList:Address", True, summarySourceCol=0,
               formula="table.getSummarySourceGroup(rec)"),
        Column(11, "count", "Int", True, summarySourceCol=0, formula="len($group)"),
        Column(12, "amount", "Numeric", True, summarySourceCol=0, formula="SUM($group.amount)"),
      ]),
    ])
    self.assertViews([
      View(1, sections=[
        Section(1, parentKey="record", tableRef=1, fields=[
          Field(1, colRef=2),
          Field(2, colRef=3),
          Field(3, colRef=4),
        ]),
      ]),
      View(2, sections=[
        Section(2, parentKey="record", tableRef=2, fields=[
          Field(4, colRef=6),
          Field(5, colRef=7),
          Field(6, colRef=8),
        ]),
      ]),
      View(3, sections=[
        Section(3, parentKey="record", tableRef=1, fields=[
          Field(7, colRef=2),
          Field(8, colRef=3),
          Field(9, colRef=4),
        ]),
        Section(4, parentKey="record", tableRef=3, fields=[
          Field(10, colRef=9),
          Field(11, colRef=11),
          Field(12, colRef=12),
        ]),
        Section(5, parentKey="record", tableRef=2, fields=[
          Field(13, colRef=6),
          Field(14, colRef=7),
          Field(15, colRef=8),
        ]),
      ]),
    ])

    # Verify the data we've loaded.
    self.assertTableData('Address', cols="subset", data=self.address_table_data)
    self.assertTableData('People', cols="subset", data=self.people_table_data)
    self.assertTableData("GristSummary_7_Address", cols="subset", data=[
      [ "id", "state", "count", "amount"          ],
      [ 1,    "NY",     7,      1.+2+6+7+8+10+11  ],
      [ 2,    "WA",     1,      3.                ],
      [ 3,    "IL",     1,      4.                ],
      [ 4,    "MA",     2,      5.+9              ],
    ])

  #----------------------------------------------------------------------

  @test_engine.test_undo
  def test_table_updates(self):
    # Verify table renames triggered by UpdateRecord actions, and related behavior.

    # Load a sample with a few table and views.
    self.init_sample_data()

    # Verify that we can rename tables via UpdatRecord actions, including multiple tables.
    self.apply_user_action(["BulkUpdateRecord", "_grist_Tables", [1,2],
                            {"tableId": ["Location", "Persons"]}])

    # Check that requested tables and summary tables got renamed correctly.
    self.assertTableData('_grist_Tables', cols="subset", data=[
      ["id",  "tableId"],
      [1,     "Location"],
      [2,     "Persons"],
      [3,     "GristSummary_8_Location"],
    ])

    # Check that reference columns to renamed tables get their type modified.
    self.assertTableData('_grist_Tables_column', rows="subset", cols="subset", data=[
      ["id",  "colId",    "type"],
      [7,     "address",  "Ref:Location"],
      [10,    "group",    "RefList:Location"],
    ])

    # Do a bulk update to rename A and B to conflicting names.
    self.apply_user_action(["AddTable", "A", [{"id": "a", "type": "Text"}]])
    out_actions = self.apply_user_action(["BulkUpdateRecord", "_grist_Tables", [1,2],
                            {"tableId": ["A", "A"]}])

    # See what doc-actions get generated.
    self.assertPartialOutActions(out_actions, {
      "stored": [
        ["ModifyColumn", "Persons", "address", {"type": "Int"}],
        ["ModifyColumn", "GristSummary_8_Location", "group", {"type": "Int"}],
        ["RenameTable", "Location", "A2"],
        ["RenameTable", "GristSummary_8_Location", "GristSummary_2_A2"],
        ["RenameTable", "Persons", "A3"],
        ["BulkUpdateRecord", "_grist_Tables", [1, 3, 2],
         {"tableId": ["A2", "GristSummary_2_A2", "A3"]}],
        ["ModifyColumn", "A3", "address", {"type": "Ref:A2"}],
        ["ModifyColumn", "GristSummary_2_A2", "group", {"type": "RefList:A2"}],
        ["BulkUpdateRecord", "_grist_Tables_column", [7, 10], {"type": ["Ref:A2", "RefList:A2"]}],
      ]
    })

    # Check that requested tables and summary tables got renamed correctly.
    self.assertTableData('_grist_Tables', cols="subset", data=[
      ["id",  "tableId"],
      [1,     "A2"],
      [2,     "A3"],
      [3,     "GristSummary_2_A2"],
      [4,     "A"],
    ])

    # Check that reference columns to renamed tables get their type modified.
    self.assertTableData('_grist_Tables_column', rows="subset", cols="subset", data=[
      ["id",  "colId",    "type"],
      [7,     "address",  "Ref:A2"],
      [10,    "group",    "RefList:A2"],
    ])

    # Verify the data we've loaded.
    self.assertTableData('A2', cols="subset", data=self.address_table_data)
    self.assertTableData('A3', cols="subset", data=self.people_table_data)
    self.assertTableData("GristSummary_2_A2", cols="subset", data=[
      [ "id", "state", "count", "amount"          ],
      [ 1,    "NY",     7,      1.+2+6+7+8+10+11  ],
      [ 2,    "WA",     1,      3.                ],
      [ 3,    "IL",     1,      4.                ],
      [ 4,    "MA",     2,      5.+9              ],
    ])

  #----------------------------------------------------------------------

  @test_engine.test_undo
  def test_table_renames_summary_by_ref(self):
    # Verify table renames when there is a group-by column that's a Reference.

    # This tests a potential bug since a table rename needs to modify Reference types, but
    # group-by columns aren't supposed to be modifiable.
    self.init_sample_data()

    # Add a table grouped by a reference column (the 'Ref:Address' column named 'address').
    self.apply_user_action(["CreateViewSection", 2, 0, 'record', [7]])
    self.assertTableData('_grist_Tables_column', cols="subset", data=[
      ["id",  "colId",    "type",           "isFormula",    "formula" ],
      [ 13,   "address",  "Ref:Address",    False,          ""        ],
      [ 14,   "group",    "RefList:People", True, "table.getSummarySourceGroup(rec)" ],
      [ 15,   "count",    "Int",            True,           "len($group)" ],
    ], rows=lambda r: (r.parentId.id == 4))

    # Now rename the table Address -> Location.
    out_actions = self.apply_user_action(["RenameTable", "Address", "Location"])

    # See what doc-actions get generated.
    self.assertPartialOutActions(out_actions, {
      "stored": [
        ["ModifyColumn", "People", "address", {"type": "Int"}],
        ["ModifyColumn", "GristSummary_7_Address", "group", {"type": "Int"}],
        ["ModifyColumn", "GristSummary_6_People", "address", {"type": "Int"}],
        ["RenameTable", "Address", "Location"],
        ["RenameTable", "GristSummary_7_Address", "GristSummary_8_Location"],
        ["BulkUpdateRecord", "_grist_Tables", [1, 3],
         {"tableId": ["Location", "GristSummary_8_Location"]}],
        ["ModifyColumn", "People", "address", {"type": "Ref:Location"}],
        ["ModifyColumn", "GristSummary_8_Location", "group", {"type": "RefList:Location"}],
        ["ModifyColumn", "GristSummary_6_People", "address", {"type": "Ref:Location"}],
        ["BulkUpdateRecord", "_grist_Tables_column", [7, 10, 13],
         {"type": ["Ref:Location", "RefList:Location", "Ref:Location"]}],
      ]
    })

    self.assertTableData('_grist_Tables_column', cols="subset", data=[
      ["id",  "colId",    "type",           "isFormula",    "formula" ],
      [ 13,   "address",  "Ref:Location",    False,          ""        ],
      [ 14,   "group",    "RefList:People", True, "table.getSummarySourceGroup(rec)" ],
      [ 15,   "count",    "Int",            True,           "len($group)" ],
    ], rows=lambda r: (r.parentId.id == 4))


  #----------------------------------------------------------------------

  @test_engine.test_undo
  def test_table_removes(self):
    # Verify table removals triggered by UpdateRecord actions, and related behavior.

    # Same setup as previous test.
    self.init_sample_data()

    # Add one more table, and one more view for tables #1 and #4 (those we are about to delete).
    self.apply_user_action(["AddEmptyTable"])
    out_actions = self.apply_user_action(["CreateViewSection", 1, 0, 'detail', None])
    self.assertEqual(out_actions.retValues[0]["viewRef"], 5)
    self.apply_user_action(["CreateViewSection", 4, 5, 'detail', None])

    # See what's in TableViews and TabBar tables, to verify after we remove a table.
    self.assertTableData('_grist_TableViews', data=[
      ["id",  "tableRef", "viewRef"],
      [1,     1,          3],
      [2,     1,          5],
    ])
    self.assertTableData('_grist_TabBar', cols="subset", data=[
      ["id",  "viewRef"],
      [1,     1],
      [2,     2],
      [3,     3],
      [4,     4],
      [5,     5],
    ])

    # Remove two tables, ensure certain views get removed.
    self.apply_user_action(["BulkRemoveRecord", "_grist_Tables", [1, 4]])

    # See that some TableViews/TabBar entries disappear, or tableRef gets unset.
    self.assertTableData('_grist_TableViews', data=[
      ["id",  "tableRef", "viewRef"],
      [1,     0,          3],
    ])
    self.assertTableData('_grist_TabBar', cols="subset", data=[
      ["id",  "viewRef"],
      [2,     2],
      [3,     3],
    ])

    # Check that reference columns to this table get removed, with associated fields.
    self.assertTables([
      Table(2, "People", primaryViewId=2, summarySourceTable=0, columns=[
        Column(5, "manualSort", "ManualSortPos", False, "", 0),
        Column(6, "name",       "Text",         False, "", 0),
        Column(8, "city",       "Any", True, "$address.city", 0),
      ]),
      # Note that the summary table is also gone.
    ])
    self.assertViews([
      View(2, sections=[
        Section(2, parentKey="record", tableRef=2, fields=[
          Field(4, colRef=6),
          Field(6, colRef=8),
        ]),
      ]),
      View(3, sections=[
        Section(5, parentKey="record", tableRef=2, fields=[
          Field(13, colRef=6),
          Field(15, colRef=8),
        ]),
      ]),
    ])
