"""
This module defines what sandbox functions are made available to the Node controller,
and starts the grist sandbox. See engine.py for the API documentation.
"""
import os
import sys
sys.path.append('thirdparty')
# pylint: disable=wrong-import-position

import marshal
import functools

import six

import actions
import engine
import migrations
import schema
import useractions
import objtypes
from acl_formula import parse_acl_formula
from sandbox import get_default_sandbox
from imports.register import register_import_parsers

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

def table_data_from_db(table_name, table_data_repr):
  if table_data_repr is None:
    return actions.TableData(table_name, [], {})
  table_data_parsed = marshal.loads(table_data_repr)
  table_data_parsed = {key.decode("utf8"): value for key, value in table_data_parsed.items()}
  id_col = table_data_parsed.pop("id")
  return actions.TableData(table_name, id_col,
                           actions.decode_bulk_values(table_data_parsed, _decode_db_value))

def _decode_db_value(value):
  # Decode database values received from SQLite's allMarshal() call. These are encoded by
  # marshalling certain types and storing as BLOBs (received in Python as binary strings, as
  # opposed to text which is received as unicode). See also encodeValue() in DocStorage.js
  t = type(value)
  if t == six.binary_type:
    return objtypes.decode_object(marshal.loads(value))
  else:
    return value

def run(sandbox):
  eng = engine.Engine()

  def export(method):
    # Wrap each method so that it logs a message that it's being called.
    @functools.wraps(method)
    def wrapper(*args, **kwargs):
      log.debug("calling %s" % method.__name__)
      return method(*args, **kwargs)

    sandbox.register(method.__name__, wrapper)

  def load_and_record_table_data(table_name, table_data_repr):
    result = table_data_from_db(table_name, table_data_repr)
    eng.record_table_stats(result, table_data_repr)
    return result

  @export
  def apply_user_actions(action_reprs, user=None):
    action_group = eng.apply_user_actions([useractions.from_repr(u) for u in action_reprs], user)
    return eng.acl_split(action_group).to_json_obj()

  @export
  def fetch_table(table_id, formulas=True, query=None):
    return actions.get_action_repr(eng.fetch_table(table_id, formulas=formulas, query=query))

  @export
  def fetch_table_schema():
    return eng.fetch_table_schema()

  @export
  def autocomplete(txt, table_id, column_id, user):
    return eng.autocomplete(txt, table_id, column_id, user)

  @export
  def find_col_from_values(values, n, opt_table_id):
    return eng.find_col_from_values(values, n, opt_table_id)

  @export
  def fetch_meta_tables(formulas=True):
    return {table_id: actions.get_action_repr(table_data)
            for (table_id, table_data) in six.iteritems(eng.fetch_meta_tables(formulas))}

  @export
  def load_meta_tables(meta_tables, meta_columns):
    return eng.load_meta_tables(load_and_record_table_data("_grist_Tables", meta_tables),
                                load_and_record_table_data("_grist_Tables_column", meta_columns))

  @export
  def load_table(table_name, table_data):
    return eng.load_table(load_and_record_table_data(table_name, table_data))

  @export
  def get_table_stats():
    return eng.get_table_stats()

  @export
  def create_migrations(all_tables, metadata_only=False):
    doc_actions = migrations.create_migrations(
      {t: table_data_from_db(t, data) for t, data in six.iteritems(all_tables)}, metadata_only)
    return [actions.get_action_repr(action) for action in doc_actions]

  @export
  def get_version():
    return schema.SCHEMA_VERSION

  @export
  def set_doc_url(doc_url):
    os.environ['DOC_URL'] = doc_url

  @export
  def get_formula_error(table_id, col_id, row_id):
    return objtypes.encode_object(eng.get_formula_error(table_id, col_id, row_id))

  export(parse_acl_formula)
  export(eng.load_empty)
  export(eng.load_done)

  register_import_parsers(sandbox)

  log.info("Ready")  # This log message is significant for checkpointing.
  sandbox.run()

def main():
  run(get_default_sandbox())

if __name__ == "__main__":
  main()
