'use strict';

const fs = require('fs');
const path = require('path');

const postcss = require('postcss');
const timsort = require('timsort').sort;

module.exports = postcss.plugin('css-declaration-sorter', function (options) {
  return function (css) {
    let sortOrderPath;

    options = options || {};

    // Use included sorting order if order is passed and not alphabetically
    if (options.order && options.order !== 'alphabetically') {
      sortOrderPath = path.join(__dirname, '../orders/', options.order) + '.json';
    } else if (options.customOrder) {
      sortOrderPath = options.customOrder;
    } else {
      // Fallback to the default sorting order
      return processCss(css, 'alphabetically');
    }

    // Load in the array containing the order from a JSON file
    return new Promise(function (resolve, reject) {
      fs.readFile(sortOrderPath, function (error, data) {
        if (error) return reject(error);
        resolve(data);
      });
    }).then(function (data) {
      return processCss(css, JSON.parse(data));
    });
  };
});

function processCss (css, sortOrder) {
  const comments = [];
  const rulesCache = [];

  css.walk(function (node) {
    const nodes = node.nodes;
    const type = node.type;

    if (type === 'comment') {
      // Don't do anything to root comments or the last newline comment
      const isNewlineNode = ~node.raws.before.indexOf('\n');
      const lastNewlineNode = isNewlineNode && !node.next();
      const onlyNode = !node.prev() && !node.next();

      if (lastNewlineNode || onlyNode || node.parent.type === 'root') {
        return;
      }

      if (isNewlineNode) {
        const pairedNode = node.next() ? node.next() : node.prev().prev();
        if (pairedNode) {
          comments.unshift({
            'comment': node,
            'pairedNode': pairedNode,
            'insertPosition': node.next() ? 'Before' : 'After',
          });
          node.remove();
        }
      } else {
        const pairedNode = node.prev() ? node.prev() : node.next().next();
        if (pairedNode) {
          comments.push({
            'comment': node,
            'pairedNode': pairedNode,
            'insertPosition': 'After',
          });
          node.remove();
        }
      }
      return;
    }

    // Add rule-like nodes to a cache so that we can remove all
    // comment nodes before we start sorting.
    const isRule = type === 'rule' || type === 'atrule';
    if (isRule && nodes && nodes.length > 1) {
      rulesCache.push(nodes);
    }
  });

  // Perform a sort once all comment nodes are removed
  rulesCache.forEach(function (nodes) {
    sortCssDecls(nodes, sortOrder);
  });

  // Add comments back to the nodes they are paired with
  comments.forEach(function (node) {
    const pairedNode = node.pairedNode;
    node.comment.remove();
    pairedNode.parent['insert' + node.insertPosition](pairedNode, node.comment);
  });
}

// Sort CSS declarations alphabetically or using the set sorting order
function sortCssDecls (cssDecls, sortOrder) {
  if (sortOrder === 'alphabetically') {
    timsort(cssDecls, function (a, b) {
      if (a.type === 'decl' && b.type === 'decl') {
        return comparator(a.prop, b.prop);
      } else {
        return compareDifferentType(a, b);
      }
    });
  } else {
    timsort(cssDecls, function (a, b) {
      if (a.type === 'decl' && b.type === 'decl') {
        const aIndex = sortOrder.indexOf(a.prop);
        const bIndex = sortOrder.indexOf(b.prop);
        return comparator(aIndex, bIndex);
      } else {
        return compareDifferentType(a, b);
      }
    });
  }
}

function comparator (a, b) {
  return a === b ? 0 : a < b ? -1 : 1;
}

function compareDifferentType (a, b) {
  if (b.type === 'atrule') { return  0; }

  return (a.type === 'decl') ? -1 : (b.type === 'decl') ? 1 : 0;
}

@@ -14,18 +14,28 @@
 %0A%0Aconst 
-fs
+%7B readFile %7D
  = requi
@@ -73,16 +73,55 @@
 'path');
+%0Aconst %7B promisify %7D = require('util');
 %0A%0Aconst 
@@ -224,16 +224,19 @@
 .plugin(
+%0A  
 'css-dec
@@ -256,107 +256,59 @@
 er',
- function (options) %7B%0A  return function (css) %7B%0A    let sortOrderPath;%0A%0A    options = options %7C%7C %7B%7D
+%0A  (options = %7B%7D) =%3E css =%3E %7B%0A    let sortOrderPath
 ;%0A%0A 
@@ -466,16 +466,24 @@
 erPath =
+%0A       
  path.jo
@@ -810,66 +810,26 @@
 urn 
-new Promise(function (resolve, reject) %7B%0A      fs.
+promisify(
 readFile
 (sor
@@ -824,16 +824,17 @@
 readFile
+)
 (sortOrd
@@ -843,152 +843,29 @@
 Path
-, function (error, data) %7B%0A        if (error) return reject(error);%0A        resolve(data);%0A      %7D);%0A    %7D).then(function 
+).then
 (data
-) %7B
+ =%3E
 %0A      
-return 
 proc
@@ -897,24 +897,20 @@
 ta))
-;
 %0A    
-%7D
 );%0A  %7D
-;%0A%7D
+%0A
 );%0A%0A
@@ -1007,32 +1007,24 @@
 css.walk
-(function 
 (node
-)
+ =%3E
  %7B%0A    c
@@ -2451,25 +2451,17 @@
 Each
-(function 
 (nodes
-)
+ =%3E
  %7B%0A 
@@ -2581,24 +2581,16 @@
 Each
-(function 
 (node
-)
+ =%3E
  %7B%0A 
@@ -2923,32 +2923,26 @@
 ssDecls,
- function
  (a, b)
+ =%3E
  %7B%0A     
@@ -3141,24 +3141,18 @@
 cls,
- function
  (a, b)
+ =%3E
  %7B%0A 
@@ -3564,24 +3564,28 @@
 rule') %7B
+%0A   
  return 
  0; %7D%0A%0A 
@@ -3576,19 +3576,20 @@
  return 
- 
 0;
+%0A 
  %7D%0A%0A  re
@@ -3593,17 +3593,16 @@
  return 
-(
 a.type =
@@ -3614,17 +3614,16 @@
 ecl'
-)
  ? -1 : 
 (b.t
@@ -3618,17 +3618,16 @@
  ? -1 : 
-(
 b.type =
@@ -3635,17 +3635,16 @@
 = 'decl'
-)
  ? 1 : 0

Use ES6 arrow functions and promisify to improve code readability