#!/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.

import database
import idlparser
import logging.config
import os
import os.path
import shutil
import tempfile
import unittest
from databasebuilder import *


class DatabaseBuilderTestCase(unittest.TestCase):

  def _create_input(self, idl_file_name, content):
    file_name = os.path.join(self._input_dir, idl_file_name)
    f = open(file_name, 'w')
    f.write(content)
    f.close()
    return file_name

  def _assert_interface_exists(self, path):
    file_path = os.path.join(self._database_dir, path)
    self.assertTrue(os.path.exists(file_path))

  def _assert_content_equals(self, path, expected_content):
    def clean(content):
      return ' '.join(filter(len, map(str.strip, content.split('\n'))))
    file_path = os.path.join(self._database_dir, path)
    self.assertTrue(os.path.exists(file_path))
    f = open(file_path, 'r')
    actual_content = f.read()
    f.close()
    if clean(actual_content) != clean(expected_content):
      msg = '''
FILE: %s
EXPECTED:
%s
ACTUAL:
%s
''' % (file_path, expected_content, actual_content)
      self.fail(msg)

  def setUp(self):
    working_dir = tempfile.mkdtemp()
    self._database_dir = os.path.join(working_dir, 'database')
    self.assertFalse(os.path.exists(self._database_dir))

    self._input_dir = os.path.join(working_dir, 'inputdir')
    os.makedirs(self._input_dir)

    self._db = database.Database(self._database_dir)
    self.assertTrue(os.path.exists(self._database_dir))

    self._builder = DatabaseBuilder(self._db)

  def tearDown(self):
    shutil.rmtree(self._database_dir)

  def test_basic_import(self):
    file_name = self._create_input('input.idl', '''
      module M {
        interface I {
          attribute int a;
        };
      };''')
    self._builder.import_idl_file(file_name)
    self._builder.merge_imported_interfaces()
    self._db.Save()
    self._assert_interface_exists('I.idl')

  def test_splitting(self):
    file_name = self._create_input('input.idl', '''
      module M {
        interface I {
          readonly attribute int a;
          int o(in int x, in optional int y);
        };
      };''')
    self._builder.import_idl_file(file_name)
    self._builder.merge_imported_interfaces()
    self._db.Save()
    self._assert_content_equals('I.idl', '''
      interface I {
        /* Attributes */
        getter attribute int a;

        /* Operations */
        int o(in int x);
        int o(in int x, in int y);
      };''')

  def test_renames(self):
    file_name = self._create_input('input.idl', '''
      module M {
        interface I {
          T op(T x);
          readonly attribute N::T attr;
        };
      };''')
    options = DatabaseBuilderOptions(type_rename_map={'I': 'i', 'T': 't'})
    self._builder.import_idl_file(file_name, options)
    self._builder.merge_imported_interfaces()
    self._db.Save()
    self._assert_content_equals('i.idl', '''
      interface i {
        /* Attributes */
        getter attribute t attr;
        /* Operations */
        t op(in t x);
      };''')

  def test_type_defs(self):
    file_name = self._create_input('input.idl', '''
      module M {
        typedef T S;
        interface I : S {
          S op(S x);
          readonly attribute S attr;
        };
      };''')
    options = DatabaseBuilderOptions()
    self._builder.import_idl_file(file_name, options)
    self._builder.merge_imported_interfaces()
    self._db.Save()
    self._assert_content_equals('I.idl', '''
      interface I :
        T {
        /* Attributes */
        getter attribute T attr;
        /* Operations */
        T op(in T x);
      };''')

  def test_merge(self):
    file_name1 = self._create_input('input1.idl', '''
      module M {
        interface I {
          const int CONST_BOTH = 0;
          const int CONST_ONLY_FIRST = 0;
          const int CONST_BOTH_DIFFERENT_VALUE = 0;

          readonly attribute int attr_only_first;
          readonly attribute int attr_both;
          readonly attribute int attr_both_readonly_difference;
          readonly attribute int attr_both_int_long_difference;

          int op_only_first();
          int op_both(int a);
          int op_both_optionals_difference(int a,
            in optional int b);
          int op_both_arg_rename(int arg);

          snippet {FIRST SNIPPET};
        };
      };''')
    self._builder.import_idl_file(file_name1,
      DatabaseBuilderOptions(source='1st',
        idl_syntax=idlparser.FREMONTCUT_SYNTAX))
    file_name2 = self._create_input('input2.idl', '''
      module M {
        interface I {
          const int CONST_BOTH = 0;
          const int CONST_ONLY_SECOND = 0;
          const int CONST_BOTH_DIFFERENT_VALUE = 1;

          readonly attribute int attr_only_second;
          readonly attribute int attr_both;
          readonly attribute long attr_both_int_long_difference;
          attribute int attr_both_readonly_difference;

          int op_only_second();
          int op_both(int a);
          int op_both_optionals_difference(int a,
            optional boolean b);
          int op_both_arg_rename(int betterName);

          snippet {SECOND SNIPPET};
        };
      };''')
    self._builder.import_idl_file(file_name2,
      DatabaseBuilderOptions(source='2nd',
        idl_syntax=idlparser.FREMONTCUT_SYNTAX))
    self._builder.set_same_signatures({'int': 'long'})
    self._builder.merge_imported_interfaces()
    self._db.Save()
    self._assert_content_equals('I.idl', '''
      @1st(module=M) @2nd(module=M) interface I {
        /* Constants */
        @1st @2nd const int CONST_BOTH = 0;
        @1st const int CONST_BOTH_DIFFERENT_VALUE = 0;
        @2nd const int CONST_BOTH_DIFFERENT_VALUE = 1;
        @1st const int CONST_ONLY_FIRST = 0;
        @2nd const int CONST_ONLY_SECOND = 0;

        /* Attributes */
        @1st @2nd getter attribute int attr_both;
        @1st @2nd getter attribute int attr_both_int_long_difference;
        @1st @2nd getter attribute int attr_both_readonly_difference;
        @2nd setter attribute int attr_both_readonly_difference;
        @1st getter attribute int attr_only_first;
        @2nd getter attribute int attr_only_second;

        /* Operations */
        @1st @2nd int op_both(in t a);
        @1st @2nd int op_both_arg_rename(in t betterName);
        @1st @2nd int op_both_optionals_difference(in t a);
        @1st int op_both_optionals_difference(in t a, in int b);
        @2nd int op_both_optionals_difference(in t a, in boolean b);
        @1st int op_only_first();
        @2nd int op_only_second();

        /* Snippets */
        @1st snippet {FIRST SNIPPET};
        @2nd snippet {SECOND SNIPPET};
      };''')

  def test_mergeDartName(self):
    file_name1 = self._create_input('input1.idl', '''
      module M {
        interface I {
          [ImplementationFunction=foo] int member(in int a);
        };
      };''')
    self._builder.import_idl_file(file_name1,
      DatabaseBuilderOptions(source='1st',
        idl_syntax=idlparser.FREMONTCUT_SYNTAX))
    file_name2 = self._create_input('input2.idl', '''
      module M {
        interface I {
          [DartName=bar] int member(in int a);
        };
      };''')
    self._builder.import_idl_file(file_name2,
      DatabaseBuilderOptions(source='2nd',
        idl_syntax=idlparser.FREMONTCUT_SYNTAX))
    self._builder.merge_imported_interfaces()
    self._db.Save()
    self._assert_content_equals('I.idl', '''
      @1st(module=M) @2nd(module=M) interface I {
        /* Operations */
        @1st @2nd [DartName=bar, ImplementationFunction=foo] int member(in int a);
      };''')

  def test_supplemental(self):
    file_name = self._create_input('input1.idl', '''
      module M {
        interface I {
          readonly attribute int a;
        };
        [Supplemental] interface I {
          readonly attribute int b;
        };
      };''')
    self._builder.import_idl_file(file_name,
      DatabaseBuilderOptions(source='Src'))
    self._builder.merge_imported_interfaces()
    self._db.Save()
    self._assert_content_equals('I.idl', '''
      @Src(module=M) interface I {
        /* Attributes */
        @Src getter attribute int a;
        @Src getter attribute int b;
      };''')

  def test_impl_stmt(self):
    file_name = self._create_input('input.idl', '''
      module M {
        interface I {};
        I implements J;
      };''')
    self._builder.import_idl_file(file_name,
      DatabaseBuilderOptions(source='Src'))
    self._builder.merge_imported_interfaces()
    self._db.Save()
    self._assert_content_equals('I.idl', '''
      @Src(module=M) interface I :
        @Src J {
      };''')

  def test_obsolete(self):
    file_name1 = self._create_input('input1.idl', '''
      module M {
        interface I {
          readonly attribute int keep;
          readonly attribute int obsolete; // Would be removed
        };
      };''')
    self._builder.import_idl_file(file_name1,
      DatabaseBuilderOptions(source='src'))
    file_name2 = self._create_input('input2.idl', '''
      module M {
        interface I {
          readonly attribute int keep;
          readonly attribute int new;
        };
      };''')
    self._builder.import_idl_file(file_name2,
      DatabaseBuilderOptions(source='src',
                   obsolete_old_declarations=True))
    self._builder.merge_imported_interfaces()
    self._db.Save()
    self._assert_content_equals('I.idl', '''
      @src(module=M) interface I {
        /* Attributes */
        @src getter attribute int keep;
        @src getter attribute int new;
      };''')

  def test_annotation_normalization(self):
    file_name = self._create_input('input.idl', '''
      module M {
        interface I : J{
          const int C = 0;
          readonly attribute int a;
          int op();
        };
      };''')
    self._builder.import_idl_file(file_name,
      DatabaseBuilderOptions(source='Src', source_attributes={'x': 'y'}))
    self._builder.merge_imported_interfaces()
    interface = self._db.GetInterface('I')
    interface.parents[0].annotations['Src']['x'] = 'u'
    interface.constants[0].annotations['Src']['z'] = 'w'
    interface.attributes[0].annotations['Src']['x'] = 'u'
    self._db.Save()

    # Before normalization
    self._assert_content_equals('I.idl', '''
      @Src(module=M, x=y)
      interface I : @Src(x=u) J {
        /* Constants */
        @Src(x=y, z=w) const int C = 0;
        /* Attributes */
        @Src(x=u) getter attribute int a;
        /* Operations */
        @Src(x=y) int op();
      };''')

    # Normalize
    self._builder.normalize_annotations(['Src'])
    self._db.Save()

    # After normalization
    self._assert_content_equals('I.idl', '''
      @Src(module=M, x=y)
      interface I : @Src(x=u) J {
        /* Constants */
        @Src(z=w) const int C = 0;
        /* Attributes */
        @Src(x=u) getter attribute int a;
        /* Operations */
        @Src int op();
      };''')

  def test_fix_displacements(self):
    file_name1 = self._create_input('input1.idl', '''
      module M {
        interface I {};
        interface J : I {
          readonly attribute int attr;
        };
      };''')
    self._builder.import_idl_file(file_name1,
      DatabaseBuilderOptions(source='1st'))
    file_name2 = self._create_input('input2.idl', '''
      module M {
        interface I {
          readonly attribute int attr;
        };
        interface J : I {};
      };''')
    self._builder.import_idl_file(file_name2,
      DatabaseBuilderOptions(source='2nd'))
    self._builder.merge_imported_interfaces()
    self._builder.fix_displacements('2nd')
    self._db.Save()
    self._assert_content_equals('J.idl', '''
      @1st(module=M) @2nd(module=M) interface J :
        @1st @2nd I {
        /* Attributes */
        @1st
        @2nd(via=I)
        getter attribute int attr;
      };''')


if __name__ == "__main__":
  logging.config.fileConfig("logging.conf")
  if __name__ == '__main__':
    unittest.main()
