#!/usr/bin/env coffee

# CSS in CS
#
# TODO: function that transforms a js object into css stylesheet with
#
#   - camelCase to "-" support.
#
#   - toString of arguments (implicit in JSON.stringify, right?)
#
#   - nesting as inheritance²
#
#   - support for ">" (direct inheritance)
#
#   - combination of inheritance with OR (","); needs some "expansion"
#     possible issue : "," in attrs
#
#   - other selection (e.g. "+") need some expansion too?
#     Study precedence of combinators?
#     See <https://stackoverflow.com/questions/3851635/css-combinator-precedence>
#
#   - support for "&" (aka parent). Or just detect stuff that start with ":"?
#     and dont put whitespace? Mmm would be limited and ambigous (ex: stuff
#     that start with "."). Have a look at <http://www.joeloliveira.com/2011/06/28/the-ampersand-a-killer-sass-feature/>
#
#   - media queries? (or don't care?)
#     yeah study use of "{" nesting in media queries, font-face, etc.
#     more generally, this is about @-rules: 
#     <https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule>
#     see also <https://css-tricks.com/the-at-rules-of-css/>
#     We should be able to nest such rules as in SASS, see e.g.
#     <https://www.sitepoint.com/sass-basics-rules-directives/>

# TODO: document features (really?), 
#       package as a npm module (name? minimal-css?), that's it.

# TODO: patch "&": should work not for one level but up to the top.

# TODO: make a command-line to go from json to css?

# TODO: option for inline CSS (<style> embedding + escape of HTML chars?)

# TODO: file-like line writer with indent level memory.

# ------------------------------------------------------------------------------

fs = require "fs" # Issue when used in the browser? Do we care?

type = (item) ->
  ts = Object::toString
  ts.call(item)[8...-1].toLowerCase()

isEmpty = (object) ->
  Object.keys(object).length == 0

dashify = (string) ->
  # Camel-case to kebab-case converter
  CAPITAL = /([A-Z])/g
  dashify_replacer = (match) -> "-" + match[0].toLowerCase()
  string.replace CAPITAL, dashify_replacer

split = (string) ->
    # split the string by (unquoted) commas and trim the parts
    pattern = ///
      (
        '[^']*'    |  # single-quoted text or ...
        "[^"]*"    |  # double-quoted text or ...
        [^,"']*    |  # 0 or more chars without quote or comma.
      )*
    ///g
    result = []
    while pattern.lastIndex <= string.length
      match = pattern.exec string 
      result.push match[0].trim()
      pattern.lastIndex += 1 # get over the comma (or past the end).
    return result

# TODO: take into account that & may be quoted?

distribute = (items) ->
   # > distribute [[1,2], [3,4], [5]]
   # [[1,3,5], [1,4,5], [2,3,5], [2,4,5]]
   if items.length is 0
     []
   else if items.length is 1
     ([item] for item in items[0])
   else
     result = []
     for head in items[0]
       for tail in distribute items[1..]
         result.push [head, tail...]
     result

linearize = (stylesheet) ->
  # stylesheet object -> a list of [[selector], rules] pairs.
  output = [] 
  for selector, kvs of stylesheet
    rules = {}
    output.push [[selector], rules]
    for k, v of kvs
      if type(v) isnt "object"
        rules[dashify(k)] = v
      else
        _stylesheet = linearize "#{k}": v
        for [_selectors, _rules] in _stylesheet
          _selectors.unshift selector
        output = output.concat _stylesheet
  output

module.exports = cssify = (stylesheet) ->
  stylesheet = linearize stylesheet
  lines = []
  for [selectors, rules] in stylesheet
     
    # filter media queries out of the selectors and consolidate them
    mqs = selectors.filter (s) -> s.startsWith "@media"
    MQ = mqs.length >= 1
    if MQ
      smqs = distribute (split(mq[7..]) for mq in mqs)
      lines.push "@media " + (smq.join(", ") for smq in smqs).join(" and ") + " {"
      indent = "  "
    else
      indent = ""           

    # consolidate "normal" selectors (handle "&" properly)
    selectors = selectors.filter (s) -> not s.startsWith "@media"

    # Distribute selectors
    selectors_list = distribute (split(selector) for selector in selectors)
#      console.log "ss:", selectors
#      console.log "sss:", (split(selector) for selector in selectors)
#      console.log "sl:", selectors_list

    selectors_sums = []
    for selectors in selectors_list
      selectors_sum = ""
      for selector in selectors
        if '&' not in selector
          selector = "& #{selector}"
        selectors_sum = (selector.replace /&/g, selectors_sum).trim()
      selectors_sums.push selectors_sum
    selectors_final = selectors_sums.join ", " 

    if not isEmpty rules
      lines.push indent + selectors_final + " {" 

      # serialize the flattened css object
      for k, v of rules
          lines.push indent + "  #{k}: #{v};"

      lines.push indent + "}"

    if MQ
      lines.push "}"
      lines.push ""

  lines.join "\n"


main = () ->
  filenames = process.argv[2..]
  for filename in filenames
    src = fs.readFileSync filename, 'utf8'
    console.log src
    console.log "**********************************"

if require.main is module
    main()




