define(['require', './normalize'], function(req, normalize) {
  var nodePrint = function() {};
  if (requirejs.tools)
    requirejs.tools.useLib(function(req) {
      req(['node/print'], function(_nodePrint) {
        nodePrint = _nodePrint;
      }, function(){});
    });
  
  var cssAPI = {};
  
  function compress(css) {
    if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
      try {
        var csso = require.nodeRequire('csso');
        var csslen = css.length;
        css = csso.justDoIt(css);
        nodePrint('Compressed CSS output to ' + Math.round(css.length / csslen * 100) + '%.');
        return css;
      }
      catch(e) {
        nodePrint('Compression module not installed. Use "npm install csso -g" to enable.');
        return css;
      }
    }
    nodePrint('Compression not supported outside of nodejs environments.');
    return css;
  }
  
  //load file code - stolen from text plugin
  function loadFile(path) {
    if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
      var fs = require.nodeRequire('fs');
      var file = fs.readFileSync(path, 'utf8');
      if (file.indexOf('\uFEFF') === 0)
        return file.substring(1);
      return file;
    }
    else {
      var file = new java.io.File(path),
        lineSeparator = java.lang.System.getProperty("line.separator"),
        input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), 'utf-8')),
        stringBuffer, line;
      try {
        stringBuffer = new java.lang.StringBuffer();
        line = input.readLine();
        if (line && line.length() && line.charAt(0) === 0xfeff)
          line = line.substring(1);
        stringBuffer.append(line);
        while ((line = input.readLine()) !== null) {
          stringBuffer.append(lineSeparator).append(line);
        }
        return String(stringBuffer.toString());
      }
      finally {
        input.close();
      }
    }
  }
  
  
  function saveFile(path, data) {
    if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
      var fs = require.nodeRequire('fs');
      fs.writeFileSync(path, data, 'utf8');
    }
    else {
      var content = new java.lang.String(data);
      var output = new java.io.BufferedWriter(new java.io.OutputStreamWriter(new java.io.FileOutputStream(path), 'utf-8'));
  
      try {
        output.write(content, 0, content.length());
        output.flush();
      }
      finally {
        output.close();
      }
    }
  }
  
  //when adding to the link buffer, paths are normalised to the baseUrl
  //when removing from the link buffer, paths are normalised to the output file path
  function escape(content) {
    return content.replace(/(["'\\])/g, '\\$1')
      .replace(/[\f]/g, "\\f")
      .replace(/[\b]/g, "\\b")
      .replace(/[\n]/g, "\\n")
      .replace(/[\t]/g, "\\t")
      .replace(/[\r]/g, "\\r");
  }

  // NB add @media query support for media imports
  var importRegEx = /@import\s*(url)?\s*(('([^']*)'|"([^"]*)")|\(('([^']*)'|"([^"]*)"|([^\)]*))\))\s*;?/g;

  var loadCSSFile = function(fileUrl) {
    var css = loadFile(fileUrl);

    // normalize the css (except import statements)
    css = normalize(css, fileUrl, baseUrl);

    // detect all import statements in the css and normalize
    var importUrls = [];
    var importIndex = [];
    var importLength = [];
    var match;
    while (match = importRegEx.exec(css)) {
      var importUrl = match[4] || match[5] || match[7] || match[8] || match[9];

      // normalize import url
      if (importUrl.substr(importUrl.length - 5, 5) != '.less' && importUrl.substr(importUrl.length - 4, 4) != '.css')
        importUrl += '.css';

      // contains a protocol
      if (importUrl.match(/:\/\//))
        continue;

      // relative to css base
      if (importUrl.substr(0, 1) == '/')
        importUrl = cssBase + importUrl;
      else
        importUrl = baseUrl + importUrl;

      importUrls.push(importUrl);
      importIndex.push(importRegEx.lastIndex - match[0].length);
      importLength.push(match[0].length);
    }

    // load the import stylesheets and substitute into the css
    for (var i = 0; i < importUrls.length; i++)
      (function(i) {
        var importCSS = loadCSSFile(importUrls[i]);
        css = css.substr(0, importIndex[i]) + importCSS + css.substr(importIndex[i] + importLength[i]);
        var lenDiff = importCSS.length - importLength[i];
        for (var j = i + 1; j < importUrls.length; j++)
          importIndex[j] += lenDiff;
      })(i);

    return css;
  }
  

  var baseUrl;  
  var cssBase;
  var curModule;
  cssAPI.load = function(name, req, load, config, parse) {
    if (!baseUrl)
      baseUrl = config.baseUrl;
    
    if (!cssBase) {
      cssBase = config.cssBase || config.appDir || baseUrl;
      if (cssBase.substr(cssBase.length - 1, 1) != '/')
        cssBase += '/';
    }

    if (config.modules) {
      //run through the module list - the first one without a layer set is the current layer we are in
      //allows to track the current layer number for layer-specific config
      for (var i = 0; i < config.modules.length; i++)
        if (config.modules[i].layer === undefined) {
          curModule = i;
          break;
        }
    }
    
    //store config
    cssAPI.config = cssAPI.config || config;

    name += !parse ? '.css' : '.less';

    var fileUrl = req.toUrl(name);

    //external URLS don't get added (just like JS requires)
    if (fileUrl.substr(0, 7) == 'http://' || fileUrl.substr(0, 8) == 'https://')
      return;

    //add to the buffer
    _cssBuffer[name] = loadCSSFile(fileUrl);

    // parse if necessary
    if (parse)
      _cssBuffer[name] = parse(_cssBuffer[name]);

    load();
  }
  
  cssAPI.normalize = function(name, normalize) {
    if (name.substr(name.length - 4, 4) == '.css')
      name = name.substr(0, name.length - 4);
    return normalize(name);
  }
  
  //list of cssIds included in this layer
  var _layerBuffer = [];
  var _cssBuffer = [];
  cssAPI.write = function(pluginName, moduleName, write, parse) {
    //external URLS don't get added (just like JS requires)
    if (moduleName.substr(0, 7) == 'http://' || moduleName.substr(0, 8) == 'https://')
      return;
    
    var resourceName = moduleName + (!parse ? '.css' : '.less');
    _layerBuffer.push(_cssBuffer[resourceName]);

    var separateCSS = false;
    if (cssAPI.config.separateCSS)
      separateCSS = true;
    if (typeof curModule == 'number' && cssAPI.config.modules[curModule].separateCSS !== undefined)
      separateCSS = cssAPI.config.modules[curModule].separateCSS;
    if (separateCSS)
      write.asModule(pluginName + '!' + moduleName, 'define(function(){})');
    else
      write("requirejs.s.contexts._.nextTick = function(f){f()}; require(['css'], function(css) { css.addBuffer('" + resourceName + "'); }); requirejs.s.contexts._.nextTick = requirejs.nextTick;");
  }
  
  cssAPI.onLayerEnd = function(write, data, parser) {
    firstWrite = true;
    //separateCSS parameter set either globally or as a layer setting
    var separateCSS = false;
    if (cssAPI.config.separateCSS)
      separateCSS = true;
    if (typeof curModule == 'number' && cssAPI.config.modules[curModule].separateCSS !== undefined)
      separateCSS = cssAPI.config.modules[curModule].separateCSS;
    curModule = null;
    
    //calculate layer css
    var css = _layerBuffer.join('');
    
    if (separateCSS) {
      nodePrint('Writing CSS! file: ' + data.name + '\n');
      
      //calculate the css output path for this layer
      var path = this.config.appDir ? this.config.baseUrl + data.name + '.css' : cssAPI.config.out.replace(/\.js$/, '.css');
      
      //renormalize the css to the output path
      var output = compress(normalize(css, baseUrl, path));
      
      saveFile(path, output);
    }
    else {
      if (css == '')
        return;
      //write the injection and layer index into the layer
      //prepare the css
      css = escape(compress(css));
      
      //the code below overrides async require functionality to ensure instant buffer injection
      write("requirejs.s.contexts._.nextTick = function(f){f()}; require(['css'], function(css) { css.setBuffer('" + css + (parser ? "', true" : "'") + "); }); requirejs.s.contexts._.nextTick = requirejs.nextTick; ");
    }
    
    //clear layer buffer for next layer
    _layerBuffer = [];
  }
  
  return cssAPI;
});
