#!/usr/bin/python
# Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.

"""Module to manage IDL files."""

import copy
import logging
import os
import os.path
import shutil
import idlnode
import idlparser
import idlrenderer

_logger = logging.getLogger('database')


class Database(object):
  """The Database class manages a collection of IDL files stored
  inside a directory.

  Each IDL is describing a single interface. The IDL files are written in the
  FremontCut syntax, which is derived from the Web IDL syntax and includes
  annotations.

  Database operations include adding, updating and removing IDL files.
  """

  def __init__(self, root_dir):
    """Initializes a Database over a given directory.

    Args:
      root_dir -- a directory. If directory does not exist, it will
      be created.
    """
    self._root_dir = root_dir
    if not os.path.exists(root_dir):
      _logger.debug('creating root directory %s' % root_dir)
      os.makedirs(root_dir)
    self._all_interfaces = {}
    self._interfaces_to_delete = []
    self._idlparser = idlparser.IDLParser(idlparser.FREMONTCUT_SYNTAX)

  def Clone(self):
    new_database = Database(self._root_dir)
    new_database._all_interfaces = copy.deepcopy(self._all_interfaces)
    new_database._interfaces_to_delete = copy.deepcopy(
        self._interfaces_to_delete)
    return new_database

  def Delete(self):
    """Deletes the database by deleting its directory"""
    if os.path.exists(self._root_dir):
      shutil.rmtree(self._root_dir)
    # reset in-memory constructs
    self._all_interfaces = {}

  def _ScanForInterfaces(self):
    """Iteratores over the database files and lists all interface names.

    Return:
      A list of interface names.
    """
    res = []

    def Visitor(_, dirname, names):
      for name in names:
        if os.path.isfile(os.path.join(dirname, name)):
          root, ext = os.path.splitext(name)
          if ext == '.idl':
            res.append(root)

    os.path.walk(self._root_dir, Visitor, None)
    return res

  def _FilePath(self, interface_name):
    """Calculates the file path that a given interface should
    be saved to.

    Args:
      interface_name -- the name of the interface.
    """
    return os.path.join(self._root_dir, '%s.idl' % interface_name)

  def _LoadInterfaceFile(self, interface_name):
    """Loads an interface from the database.

    Returns:
      An IDLInterface instance or None if the interface is not found.
    Args:
      interface_name -- the name of the interface.
    """
    file_name = self._FilePath(interface_name)
    _logger.info('loading %s' % file_name)
    if not os.path.exists(file_name):
      return None

    f = open(file_name, 'r')
    content = f.read()
    f.close()

    # Parse file:
    idl_file = idlnode.IDLFile(self._idlparser.parse(content), file_name)

    if not idl_file.interfaces:
      raise RuntimeError('No interface found in %s' % file_name)
    elif len(idl_file.interfaces) > 1:
      raise RuntimeError('Expected one interface in %s' % file_name)

    interface = idl_file.interfaces[0]
    self._all_interfaces[interface_name] = interface
    return interface

  def Load(self):
    """Loads all interfaces into memory.
    """
    # FIXME: Speed this up by multi-threading.
    for (interface_name) in self._ScanForInterfaces():
      self._LoadInterfaceFile(interface_name)

  def Save(self):
    """Saves all in-memory interfaces into files."""
    for interface in self._all_interfaces.values():
      self._SaveInterfaceFile(interface)
    for interface_name in self._interfaces_to_delete:
      self._DeleteInterfaceFile(interface_name)

  def _SaveInterfaceFile(self, interface):
    """Saves an interface into the database.

    Args:
      interface -- an IDLInterface instance.
    """

    interface_name = interface.id

    # Actual saving
    file_path = self._FilePath(interface_name)
    _logger.debug('writing %s' % file_path)

    dir_name = os.path.dirname(file_path)
    if not os.path.exists(dir_name):
      _logger.debug('creating directory %s' % dir_name)
      os.mkdir(dir_name)

    # Render the IDLInterface object into text.
    text = idlrenderer.render(interface)

    f = open(file_path, 'w')
    f.write(text)
    f.close()

  def HasInterface(self, interface_name):
    """Returns True if the interface is in memory"""
    return interface_name in self._all_interfaces

  def GetInterface(self, interface_name):
    """Returns an IDLInterface corresponding to the interface_name
    from memory.

    Args:
      interface_name -- the name of the interface.
    """
    if interface_name not in self._all_interfaces:
      raise RuntimeError('Interface %s is not loaded' % interface_name)
    return self._all_interfaces[interface_name]

  def AddInterface(self, interface):
    """Returns an IDLInterface corresponding to the interface_name
    from memory.

    Args:
      interface -- the name of the interface.
    """
    interface_name = interface.id
    if interface_name in self._all_interfaces:
      raise RuntimeError('Interface %s already exists' % interface_name)
    self._all_interfaces[interface_name] = interface

  def GetInterfaces(self):
    """Returns a list of all loaded interfaces."""
    res = []
    for _, interface in sorted(self._all_interfaces.items()):
      res.append(interface)
    return res

  def DeleteInterface(self, interface_name):
    """Deletes an interface from the database. File is deleted when
    Save() is called.

    Args:
      interface_name -- the name of the interface.
    """
    if interface_name not in self._all_interfaces:
      raise RuntimeError('Interface %s not found' % interface_name)
    self._interfaces_to_delete.append(interface_name)
    del self._all_interfaces[interface_name]

  def _DeleteInterfaceFile(self, interface_name):
    """Actual file deletion"""
    file_path = self._FilePath(interface_name)
    if os.path.exists(file_path):
      _logger.debug('deleting %s' % file_path)
      os.remove(file_path)
