import {
  AsyncTestCompleter,
  beforeEach,
  beforeEachBindings,
  ddescribe,
  describe,
  el,
  expect,
  iit,
  inject,
  it,
  xit,
} from 'angular2/test_lib';
import {StyleInliner} from 'angular2/src/core/render/dom/compiler/style_inliner';

import {isBlank} from 'angular2/src/core/facade/lang';
import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async';
import {Map, MapWrapper} from 'angular2/src/core/facade/collection';

import {XHR} from 'angular2/src/core/render/xhr';

import {bind} from 'angular2/di';

export function main() {
  describe('StyleInliner', () => {
    beforeEachBindings(() => [
      bind(XHR)
          .toClass(FakeXHR),
    ]);

    describe('loading', () => {

      it('should return a string when there is no import statement',
         inject([StyleInliner], (inliner) => {
           var css = '.main {}';
           var loadedCss = inliner.inlineImports(css, 'http://base');
           expect(loadedCss).toEqual(css);
         }));

      it('should inline @import rules',
         inject([XHR, StyleInliner, AsyncTestCompleter], (xhr, inliner, async) => {
           xhr.reply('http://base/one.css', '.one {}');
           var css = '@import url("one.css");.main {}';
           var loadedCss = inliner.inlineImports(css, 'http://base');
           expect(loadedCss).toBePromise();
           PromiseWrapper.then(loadedCss,
                               function(css) {
                                 expect(css).toEqual('.one {}\n.main {}');
                                 async.done();
                               },
                               function(e) { throw 'fail;' });
         }));

      it('should support url([unquoted url]) in @import rules',
         inject([XHR, StyleInliner, AsyncTestCompleter], (xhr, inliner, async) => {
           xhr.reply('http://base/one.css', '.one {}');
           var css = '@import url(one.css);.main {}';
           var loadedCss = inliner.inlineImports(css, 'http://base');
           expect(loadedCss).toBePromise();
           PromiseWrapper.then(loadedCss,
                               function(css) {
                                 expect(css).toEqual('.one {}\n.main {}');
                                 async.done();
                               },
                               function(e) { throw 'fail;' });
         }));

      it('should handle @import error gracefuly',
         inject([StyleInliner, AsyncTestCompleter], (inliner, async) => {
           var css = '@import "one.css";.main {}';
           var loadedCss = inliner.inlineImports(css, 'http://base');
           expect(loadedCss).toBePromise();
           PromiseWrapper.then(
               loadedCss,
               function(css) {
                 expect(css).toEqual('/* failed to import http://base/one.css */\n.main {}');
                 async.done();
               },
               function(e) { throw 'fail;' });
         }));

      it('should inline multiple @import rules',
         inject([XHR, StyleInliner, AsyncTestCompleter], (xhr, inliner, async) => {
           xhr.reply('http://base/one.css', '.one {}');
           xhr.reply('http://base/two.css', '.two {}');
           var css = '@import "one.css";@import "two.css";.main {}';
           var loadedCss = inliner.inlineImports(css, 'http://base');
           expect(loadedCss).toBePromise();
           PromiseWrapper.then(loadedCss,
                               function(css) {
                                 expect(css).toEqual('.one {}\n.two {}\n.main {}');
                                 async.done();
                               },
                               function(e) { throw 'fail;' });
         }));

      it('should inline nested @import rules',
         inject([XHR, StyleInliner, AsyncTestCompleter], (xhr, inliner, async) => {
           xhr.reply('http://base/one.css', '@import "two.css";.one {}');
           xhr.reply('http://base/two.css', '.two {}');
           var css = '@import "one.css";.main {}';
           var loadedCss = inliner.inlineImports(css, 'http://base/');
           expect(loadedCss).toBePromise();
           PromiseWrapper.then(loadedCss,
                               function(css) {
                                 expect(css).toEqual('.two {}\n.one {}\n.main {}');
                                 async.done();
                               },
                               function(e) { throw 'fail;' });
         }));

      it('should handle circular dependencies gracefuly',
         inject([XHR, StyleInliner, AsyncTestCompleter], (xhr, inliner, async) => {
           xhr.reply('http://base/one.css', '@import "two.css";.one {}');
           xhr.reply('http://base/two.css', '@import "one.css";.two {}');
           var css = '@import "one.css";.main {}';
           var loadedCss = inliner.inlineImports(css, 'http://base/');
           expect(loadedCss).toBePromise();
           PromiseWrapper.then(loadedCss,
                               function(css) {
                                 expect(css).toEqual('.two {}\n.one {}\n.main {}');
                                 async.done();
                               },
                               function(e) { throw 'fail;' });
         }));

      it('should handle invalid @import fracefuly',
         inject([StyleInliner, AsyncTestCompleter], (inliner, async) => {
           // Invalid rule: the url is not quoted
           var css = '@import one.css;.main {}';
           var loadedCss = inliner.inlineImports(css, 'http://base/');
           expect(loadedCss).toBePromise();
           PromiseWrapper.then(
               loadedCss,
               function(css) {
                 expect(css).toEqual('/* Invalid import rule: "@import one.css;" */.main {}');
                 async.done();
               },
               function(e) { throw 'fail;' });
         }));
    });

    describe('media query', () => {
      it('should wrap inlined content in media query',
         inject([XHR, StyleInliner, AsyncTestCompleter], (xhr, inliner, async) => {
           xhr.reply('http://base/one.css', '.one {}');
           var css = '@import "one.css" (min-width: 700px) and (orientation: landscape);';
           var loadedCss = inliner.inlineImports(css, 'http://base/');
           expect(loadedCss).toBePromise();
           PromiseWrapper.then(
               loadedCss,
               function(css) {
                 expect(css).toEqual(
                     '@media (min-width: 700px) and (orientation: landscape) {\n.one {}\n}\n');
                 async.done();
               },
               function(e) { throw 'fail;' });
         }));
    });

    describe('url rewritting', () => {
      it('should rewrite url in inlined content',
         inject([XHR, StyleInliner, AsyncTestCompleter], (xhr, inliner, async) => {
           // it should rewrite both '@import' and 'url()'
           xhr.reply('http://base/one.css',
                     '@import "./nested/two.css";.one {background-image: url("one.jpg");}');
           xhr.reply('http://base/nested/two.css',
                     '.two {background-image: url("../img/two.jpg");}');
           var css = '@import "one.css";';
           var loadedCss = inliner.inlineImports(css, 'http://base/');
           expect(loadedCss).toBePromise();
           PromiseWrapper.then(
               loadedCss,
               function(css) {
                 expect(css).toEqual(".two {background-image: url('http://base/img/two.jpg');}\n" +
                                     ".one {background-image: url('http://base/one.jpg');}\n");
                 async.done();
               },
               function(e) { throw 'fail;' });
         }));
    });
  });
}

class FakeXHR extends XHR {
  _responses: Map<string, string>;

  constructor() {
    super();
    this._responses = new Map();
  }

  get(url: string): Promise<string> {
    var response = this._responses.get(url);
    if (isBlank(response)) {
      return PromiseWrapper.reject('xhr error', null);
    }

    return PromiseWrapper.resolve(response);
  }

  reply(url: string, response: string) { this._responses.set(url, response); }
}
