<!-- 
Copyright (c) 2014 The Polymer Project Authors.  All rights reserved. 
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<!-- 

The `core-style` element helps manage styling inside other elements and can 
be used to make themes.  The `core-style` element can be either a producer 
or consumer of styling.  If it has its `id` property set, it's a producer.  
Elements that are producers should include css styling as their text content. 
If a `core-style` has its `ref` property set, it's a consumer.  A `core-style`
typically sets its `ref` property to the value of the `id` property of the
`core-style` it wants to use.  This allows a single producer to be used in 
multiple places, for example, in many different elements. 

It's common to place `core-style` producer elements inside HTMLImports. 
Remote stylesheets should be included this way, the &#64;import css mechanism is
not currently supported. 

Here's a basic example:

    <polymer-element name="x-test" noscript>
      <template>
        <core-style ref="x-test"></core-style>
        <content></content>
      </template>
    </polymer-element>

The `x-test` element above will be styled by any `core-style` elements that have
`id` set to `x-test`.  These `core-style` producers are separate from the element
definition, allowing a user of the element to style it independent of the author's 
styling.  For example:

    <core-style id="x-test">
      :host {
        backgound-color: steelblue;
      }
    </core-style>

The content of the `x-test` `core-style` producer gets included inside the
shadowRoot of the `x-test` element.  If the content of the `x-test` producer
`core-style` changes, all consumers of it are automatically kept in sync.  This
allows updating styling on the fly. 

The `core-style` element also supports bindings, in which case the producer
`core-style` element is the model.  Here's an example:

    <core-style id="x-test">
      :host {
        background-color: {{myColor}};
      }
    </core-style>
    <script>
      document._currentScript.ownerDocument.getElementById('x-test').myColor = 'orange';
    </script>

Finally, to facilitate sharing data between `core-style` elements, all
`core-style` elements have a `g` property which is set to the global 
`CoreStyle.g`.  Here's an example:

    <core-style id="x-test">
      :host {
        background-color: {{g.myColor}};
      }
    </core-style>
    <script>
      CoreStyle.g.myColor = 'tomato';
    </script>

Finally, one `core-style` can be nested inside another.  The `core-style`
element has a `list` property which is a map of all the `core-style` producers. 
A `core-style` producer's content is available via its `cssText` property.  
Putting this together:

    <core-style id="common">
      :host {
        font-family: sans-serif;
      }
    </core-style>

    <core-style id="x-test">
      {{list.common.cssText}}

      :host {
        background-color: {{g.myColor}};
      }
    </core-style>


@group Polymer Core Elements
@element core-style
@homepage github.io
-->

<link rel="import" href="../polymer/polymer.html">

<polymer-element name="core-style" hidden>
<script>
(function() {

window. CoreStyle = window. CoreStyle || {
  g: {},
  list: {},
  refMap: {}
};

Polymer('core-style', {
  /**
   * The `id` property should be set if the `core-style` is a producer
   * of styles.  In this case, the `core-style` should have text content
   * that is cssText. 
   *
   * @attribute id
   * @type string
   * @default ''
   */


  publish: {
    /**
     * The `ref` property should be set if the `core-style` element is a 
     * consumer of styles.  Set it to the `id` of the desired `core-style`
     * element. 
     *
     * @attribute ref
     * @type string
     * @default ''
     */
    ref: ''
  },

  // static
  g: CoreStyle.g,
  refMap: CoreStyle.refMap,

  /**
   * The `list` is a map of all `core-style` producers stored by `id`.  It 
   * should be considered readonly.  It's useful for nesting one `core-style`
   * inside another. 
   *
   * @attribute list
   * @type object (readonly)
   * @default {map of all `core-style` producers}
   */
  list: CoreStyle.list,

  // if we have an id, we provide style
  // if we have a ref, we consume/require style
  ready: function() {
    if (this.id) {
      this.provide();
    } else {
      this.registerRef(this.ref);
      if (! window. ShadowDOMPolyfill) {
        this.require();
      }  
    }
  },

  // can't shim until attached if using SD polyfill because need to find host
  attached: function() {
    if (! this.id && window. ShadowDOMPolyfill) {
      this.require();
    }
  },

  /****** producer stuff *******/

  provide: function() {
    this.register();
    // we want to do this asap, especially so we can do so before definitions
    // that use this core-style are registered. 
    if (this.textContent) {
      this._completeProvide();
    } else {
      this.async(this._completeProvide);
    }
  },

  register: function() {
    var i = this.list[this.id];
    if (i) {
      if (! Array.isArray(i)) {
        this.list[this.id] = [i];
      }
      this.list[this.id].push(this);
    } else {
      this.list[this.id] = this;  
    }
  },

  // stamp into a shadowRoot so we can monitor dom of the bound output
  _completeProvide: function() {
    this.createShadowRoot();
    this.domObserver = new MutationObserver(this.domModified.bind(this))
        .observe(this.shadowRoot, {subtree: true, 
        characterData: true, childList: true});
    this.provideContent();
  },

  provideContent: function() {
    this.ensureTemplate();
    this.shadowRoot.textContent = '';
    this.shadowRoot.appendChild(this.instanceTemplate(this.template));
    this.cssText = this.shadowRoot.textContent;
  },

  ensureTemplate: function() {
    if (! this.template) {
      this.template = this.querySelector('template:not([repeat]):not([bind])');
      // move content into the template
      if (! this.template) {
        this.template = document.createElement('template');
        var n = this.firstChild;
        while (n) {
          this.template.content.appendChild(n.cloneNode(true));
          n = n.nextSibling;
        }
      }
    }
  },

  domModified: function() {
    this.cssText = this.shadowRoot.textContent;
    this.notify();
  },

  // notify instances that reference this element
  notify: function() {
    var s$ = this.refMap[this.id];
    if (s$) {
      for (var i=0, s; (s=s$[i]); i++) {
        s.require();
      }
    }
  },

  /****** consumer stuff *******/

  registerRef: function(ref) {
    //console.log('register', ref);
    this.refMap[this.ref] = this.refMap[this.ref] || [];
    this.refMap[this.ref].push(this);
  },

  applyRef: function(ref) {
    this.ref = ref;
    this.registerRef(this.ref);
    this.require();
  },

  require: function() {
    var cssText = this.cssTextForRef(this.ref);
    //console.log('require', this.ref, cssText);
    if (cssText) {
      this.ensureStyleElement();
      // do nothing if cssText has not changed
      if (this.styleElement._cssText === cssText) {
        return;
      }
      this.styleElement._cssText = cssText;
      if (window. ShadowDOMPolyfill) {
        this.styleElement.textContent = cssText;
        cssText = WebComponents. ShadowCSS.shimStyle(this.styleElement,
            this.getScopeSelector());
      }
      this.styleElement.textContent = cssText;
    }
  },

  cssTextForRef: function(ref) {
    var s$ = this.byId(ref);
    var cssText = '';
    if (s$) {
      if (Array.isArray(s$)) {
        var p = [];
        for (var i=0, l=s$.length, s; (i<l) && (s=s$[i]); i++) {
          p.push(s.cssText);
        }
        cssText = p.join('\n\n');
      } else {
        cssText = s$.cssText;
      }
    }
    if (s$ && ! cssText) {
      console.warn('No styles provided for ref:', ref);
    }
    return cssText;
  },

  byId: function(id) {
    return this.list[id];
  },

  ensureStyleElement: function() {
    if (! this.styleElement) {
      this.styleElement = window. ShadowDOMPolyfill ?  
          this.makeShimStyle() :
          this.makeRootStyle();
    }
    if (! this.styleElement) {
      console.warn(this.localName, 'could not setup style.'); 
    }
  },

  makeRootStyle: function() {
    var style = document.createElement('style');
    this.appendChild(style);
    return style;
  },

  makeShimStyle: function() {
    var host = this.findHost(this);
    if (host) {
      var name = host.localName;
      var style = document.querySelector('style[' + name + '=' + this.ref +']');
      if (! style) {
        style = document.createElement('style');
        style.setAttribute(name, this.ref);
        document.head.appendChild(style);
      }
      return style;
    }
  },

  getScopeSelector: function() {
    if (! this._scopeSelector) {
      var selector = '', host = this.findHost(this);
      if (host) {
        var typeExtension = host.hasAttribute('is');
        var name = typeExtension ?  host.getAttribute('is') : host.localName;
        selector = WebComponents. ShadowCSS.makeScopeSelector(name, 
            typeExtension);
      }
      this._scopeSelector = selector;
    }
    return this._scopeSelector;
  },

  findHost: function(node) {
    while (node.parentNode) {
      node = node.parentNode;
    }
    return node.host || wrap(document.documentElement);
  },

  /* filters! */ 
  // TODO(dfreedm): add more filters! 

  cycle: function(rgb, amount) {
    if (rgb.match('#')) {
      var o = this.hexToRgb(rgb);
      if (! o) {
        return rgb;
      }
      rgb = 'rgb(' + o.r + ',' + o.b + ',' + o.g + ')';
    }

    function cycleChannel(v) {
      return Math.abs((Number(v) - amount) % 255);
    }

    return rgb.replace(/rgb\(([^,]*),([^,]*),([^,]*)\)/, function(m, a, b, c) {
      return 'rgb(' + cycleChannel(a) + ',' + cycleChannel(b) + ', ' 
          + cycleChannel(c) + ')';
    });
  },

  hexToRgb: function(hex) {
    var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? { 
        r: parseInt(result[1], 16),
        g: parseInt(result[2], 16),
        b: parseInt(result[3], 16)
    } : null;
  }

});


})();
</script>
</polymer-element>
