<commit_before>'use strict';

const prefix = 'ams-unlocked';
const showHistoryClass = `${prefix}-showhistory`;
const chartId = `${prefix}-chart`;
const chartClass = `${prefix}-chart-btn`;
const span = {
    second: 1000,
    minute: 1000 * 60,
    hour:   1000 * 60 * 60,
    day:    1000 * 60 * 60 * 24,
};

const charts = [
    { column: 6, label: "Impressions / hour", config: {metric: 'impressions', rate: 'hour'} },
    { column: 7, label: "Clicks / day", config: {metric: 'clicks', rate: 'day'} },
    { column: 10, label: "Sales / day", config: {metric: 'salesCount', rate: 'day'} },
];

const unlockSvg = chrome.runtime.getURL('images/unlock.svg');

window.setInterval(() => {
    let tableRows = $('#campaignTable tbody tr');
    addChartButtons(tableRows);

    let dashboard = $('#campaignDashboard');
    if (dashboard.find(`#${chartId}`).length == 0) {
        dashboard.append($(`<div id="${chartId}"></div>`));
    }
}, 100);

function addChartButtons(rows) {
    for (let row of rows) {
        for (let chart of charts) {
            let cells = $(row).children();
            let cell = cells[chart.column];
            if (cell && $(cell).find(`.${chartClass}`).length == 0) {
                let select = $(cells[0]).find('select')[0];
                if (!select)
                    continue;
                let selectName = select.name;
                let campaignId = selectName.split('_').pop();
                campaignId = campaignId.substring(0, 22); // first 22 chars are the campaignId; timestamp is appended for some reason
                let btn = $(`<a href="#" class="${chartClass}">Chart</a>`);
                btn.click(function(evt) {
                    console.log("charting campaign", campaignId, chart.label);
                    getDataHistory(getEntityId(), campaignId, (data) => {
                        renderChart(data, chart);
                    });
                });
                $(cell).append(btn);
            }
        }
    }
}

chrome.runtime.sendMessage({
    action: 'setSession',
    entityId: getEntityId(),
    cookies: document.cookie,
});

function getEntityId() {
    let qstring = window.location.search.substring(1);
    let qs = qstring.split('&');
    for (let q of qs) {
        let parts = q.split('=');
        if (parts[0] == 'entityId') {
            return parts[1];
        }
    }
    return undefined;
}

function getDataHistory(entityId, campaignId, cb) {
    chrome.runtime.sendMessage({
        action: 'getDataHistory',
        entityId: entityId,
        campaignId: campaignId,
    },
    (response) => {
        console.log('data response', response);
        cb(response.data);
    });
}

function renderChart(data, opt) {
    var data = transformHistoryData(data, opt.config);

    var series = {
      x: data.timestamps,
      y: data[opt.config.metric],
      mode: 'lines+markers',
      name: opt.config.metric,
      line: {shape: 'spline'},
      connectgaps: true
    };

    var layout = {
      title: opt.label,
      height: 600,
      width: $('#campaignDashboard').width(),
      autosize: true,
    };

    Plotly.newPlot(chartId, [series], layout);

    let $chart = $('#'+chartId);
    $chart.slideDown();
};

function transformHistoryData(data, opt) {
    // We have a series of timestamped snapshots; we want a series of parallel
    // arrays keyed by campaignId
    let metric = opt.metric;
    let c = {
        timestamps: [],
        [metric]: []
    };
    let lastItem;
    for (let item of data) {
        // XXX: quick hack -- do this in backend?
        if (lastItem && lastItem[metric] >= item[metric]) {
                continue;
        }

        c.timestamps.push(new Date(item.timestamp).toISOString());
        if (opt.rate) {
            if (lastItem) {
                let timeDiff = item.timestamp - lastItem.timestamp;
                let denom = timeDiff/span[opt.rate];
                c[metric].push((item[metric] - lastItem[metric])/denom);
            }
        }
        else {
            c[metric].push(item[metric]);
        }

        lastItem = item;
    }

    return c;
}
<commit_msg>don't spline any more, looks weird<commit_after>'use strict';

const prefix = 'ams-unlocked';
const showHistoryClass = `${prefix}-showhistory`;
const chartId = `${prefix}-chart`;
const chartClass = `${prefix}-chart-btn`;
const span = {
    second: 1000,
    minute: 1000 * 60,
    hour:   1000 * 60 * 60,
    day:    1000 * 60 * 60 * 24,
};

const charts = [
    { column: 6, label: "Impressions / hour", config: {metric: 'impressions', rate: 'hour'} },
    { column: 7, label: "Clicks / day", config: {metric: 'clicks', rate: 'day'} },
    { column: 10, label: "Sales / day", config: {metric: 'salesCount', rate: 'day'} },
];

const unlockSvg = chrome.runtime.getURL('images/unlock.svg');

window.setInterval(() => {
    let tableRows = $('#campaignTable tbody tr');
    addChartButtons(tableRows);

    let dashboard = $('#campaignDashboard');
    if (dashboard.find(`#${chartId}`).length == 0) {
        dashboard.append($(`<div id="${chartId}"></div>`));
    }
}, 100);

function addChartButtons(rows) {
    for (let row of rows) {
        for (let chart of charts) {
            let cells = $(row).children();
            let cell = cells[chart.column];
            if (cell && $(cell).find(`.${chartClass}`).length == 0) {
                let select = $(cells[0]).find('select')[0];
                if (!select)
                    continue;
                let selectName = select.name;
                let campaignId = selectName.split('_').pop();
                campaignId = campaignId.substring(0, 22); // first 22 chars are the campaignId; timestamp is appended for some reason
                let btn = $(`<a href="#" class="${chartClass}">Chart</a>`);
                btn.click(function(evt) {
                    console.log("charting campaign", campaignId, chart.label);
                    getDataHistory(getEntityId(), campaignId, (data) => {
                        renderChart(data, chart);
                    });
                });
                $(cell).append(btn);
            }
        }
    }
}

chrome.runtime.sendMessage({
    action: 'setSession',
    entityId: getEntityId(),
    cookies: document.cookie,
});

function getEntityId() {
    let qstring = window.location.search.substring(1);
    let qs = qstring.split('&');
    for (let q of qs) {
        let parts = q.split('=');
        if (parts[0] == 'entityId') {
            return parts[1];
        }
    }
    return undefined;
}

function getDataHistory(entityId, campaignId, cb) {
    chrome.runtime.sendMessage({
        action: 'getDataHistory',
        entityId: entityId,
        campaignId: campaignId,
    },
    (response) => {
        console.log('data response', response);
        cb(response.data);
    });
}

function renderChart(data, opt) {
    var data = transformHistoryData(data, opt.config);

    var series = {
      x: data.timestamps,
      y: data[opt.config.metric],
      mode: 'lines+markers',
      name: opt.config.metric,
      connectgaps: true
    };

    var layout = {
      title: opt.label,
      height: 600,
      width: $('#campaignDashboard').width(),
      autosize: true,
    };

    Plotly.newPlot(chartId, [series], layout);

    let $chart = $('#'+chartId);
    $chart.slideDown();
};

function transformHistoryData(data, opt) {
    // We have a series of timestamped snapshots; we want a series of parallel
    // arrays keyed by campaignId
    let metric = opt.metric;
    let c = {
        timestamps: [],
        [metric]: []
    };
    let lastItem;
    for (let item of data) {
        // XXX: quick hack -- do this in backend?
        if (lastItem && lastItem[metric] >= item[metric]) {
                continue;
        }

        c.timestamps.push(new Date(item.timestamp).toISOString());
        if (opt.rate) {
            if (lastItem) {
                let timeDiff = item.timestamp - lastItem.timestamp;
                let denom = timeDiff/span[opt.rate];
                c[metric].push((item[metric] - lastItem[metric])/denom);
            }
        }
        else {
            c[metric].push(item[metric]);
        }

        lastItem = item;
    }

    return c;
}
<|endoftext|>


<commit_before>(function(instancesController) {
  instancesController.init = function(app) {
    app.post('/api/instances', require('./instanceCreate'));
    app.get('/api/instances/:instanceId', require('./instanceGet'));
  };
}(module.exports));
<commit_msg>S-51508 Removed modules.export from IIFE<commit_after>(function(instancesController) {
  instancesController.init = function(app) {
    app.post('/api/instances', require('./instanceCreate'));
    app.get('/api/instances/:instanceId', require('./instanceGet'));
  };
}(module.exports));<|endoftext|>


<commit_before>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     451
// ==/UserScript==
<commit_msg>AUTO-CREATED Release Artifact v452 c/o Travis<commit_after>// ==UserScript==
// @author      Robert Slifka (github @rslifka)
// @connect     docs.google.com
// @connect     rslifka.github.io
// @copyright   2017-2018, Robert Slifka (https://github.com/rslifka/fate_of_all_fools)
// @description Enhancements to the Destiny Item Manager
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_getValue
// @grant       GM_log
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @homepage    https://github.com/rslifka/fate_of_all_fools
// @license     MIT; https://raw.githubusercontent.com/rslifka/fate_of_all_fools/master/LICENSE.txt
// @match       https://*.destinyitemmanager.com/*
// @name        Fate of All Fools
// @require     https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.js
// @resource    fateOfAllFoolsCSS https://rslifka.github.io/fate_of_all_fools/fateOfAllFools.css
// @run-at      document-start
// @supportURL  https://github.com/rslifka/fate_of_all_fools/issues
// @version     452
// ==/UserScript==
<|endoftext|>


<commit_before>var AdminLTEOptions = {
    //Bootstrap.js tooltip
    enableBSToppltip: false
};
angular.module('myApp', [], function($interpolateProvider) {
    $interpolateProvider.startSymbol('<%');
    $interpolateProvider.endSymbol('%>');
}).controller("MyController", function(){
    var vm = this;
    vm.onSubCategoryChange = function (selectedId) {
        vm.selectedSubCatId = selectedId;
    }
    vm.isShowSubmitButton = function (selectedCatId) {
        if (selectedCatId === "67" && vm.selectedSubCatId) {
            return true;
        } else if (selectedCatId && selectedCatId !== "67") {
            return true;
        }
        return false;
    }
});

angular.module('myApp').directive('select2', function() {
    return {
        scope: {
            theme: "@",
            multiple: "@",
            maxSelectionLength: "@"
        },
        link: function(scope, element, attr) {
            var configObj;
            if (scope.multiple) {
                configObj = {
                    theme: scope.theme || "bootstrap",
                    maximumSelectionLength: scope.maxSelectionLength
                }
            } else {
                configObj = {
                    theme: scope.theme || "default single",
                    dropdownAutoWidth: true,
                    width: '100%'
                };
            }
            jQuery(element[0])
            .select2(configObj)
            .on('change', function() {
                $(this).valid();
            });
        }
    };
});

angular.module('myApp').directive('imageUpload', function() {
    return {
        link: function(scope, element, attr) {
            jQuery(element[0]).imageupload();
        }
    };
});

angular.module('myApp').directive('datePicker', function () {
    return {
        restrict: "C",
        scope: {
            dateFormat: "@",
            minDate: "@"
        },
        link: function (scope, elem, attr) {
            jQuery(elem[0]).datepicker({
                dateFormat: scope.dateFormat,
                minDate: scope.minDate,
                onSelect: function () {
                    $(this).trigger("keyup");
                }
            });
        }
    };
});

angular.module('myApp').directive('wysiwyg', function() {
    return {
        restrict: "A",
        link: function (scope, elem, attr) {
            CKEDITOR.replace(elem[0], {
                uiColor: '#CCEAEE',
                allowedContent: true,
                enterMode : CKEDITOR.ENTER_BR,
                shiftEnterMode: CKEDITOR.ENTER_P,
                autoParagraph: false,
                filebrowserBrowseUrl: '/sysadmin/files/ckeditor',
                filebrowserImageUploadUrl: '/admin/file_upload',
                extraPlugins: 'uploadimage',
                uploadUrl: '/admin/file_upload'
            });
        }
    };
});

angular.module('myApp').directive('selectize', function() {
    return {
        link: function(scope, element, attr) {
            jQuery(element[0]).selectize({
                create: true,
                sortField: 'text'
            }).on('change', function() {
                $(this).valid();
            });
        }
    };
});
<commit_msg>Fixes from app<commit_after>var AdminLTEOptions = {
    //Bootstrap.js tooltip
    enableBSToppltip: false
};
angular.module('myApp', [], function($interpolateProvider) {
    $interpolateProvider.startSymbol('<%');
    $interpolateProvider.endSymbol('%>');
}).controller("MyController", function(){
    var vm = this;
    vm.onCategoryChange = function () {
        vm.subCategory = "";
    }
    vm.isShowSubmitButton = function (selectedCatId) {
        if (selectedCatId === vm.CategoryIdHasSubCat && vm.subCategory) {
            return true;
        } else if (selectedCatId && selectedCatId !== vm.CategoryIdHasSubCat) {
            return true;
        }
        return false;
    }
    vm.init = function (catId) {
        vm.CategoryIdHasSubCat = catId;
    }
});

angular.module('myApp').directive('select2', function() {
    return {
        scope: {
            theme: "@",
            multiple: "@",
            maxSelectionLength: "@"
        },
        link: function(scope, element, attr) {
            var configObj;
            if (scope.multiple) {
                configObj = {
                    theme: scope.theme || "bootstrap",
                    maximumSelectionLength: scope.maxSelectionLength
                }
            } else {
                configObj = {
                    theme: scope.theme || "default single",
                    dropdownAutoWidth: true,
                    width: '100%'
                };
            }
            jQuery(element[0])
            .select2(configObj)
            .on('change', function() {
                $(this).valid();
            });
        }
    };
});

angular.module('myApp').directive('imageUpload', function() {
    return {
        link: function(scope, element, attr) {
            jQuery(element[0]).imageupload();
        }
    };
});

angular.module('myApp').directive('datePicker', function () {
    return {
        restrict: "C",
        scope: {
            dateFormat: "@",
            minDate: "@"
        },
        link: function (scope, elem, attr) {
            jQuery(elem[0]).datepicker({
                dateFormat: scope.dateFormat,
                minDate: scope.minDate,
                onSelect: function () {
                    $(this).trigger("keyup");
                }
            });
        }
    };
});

angular.module('myApp').directive('wysiwyg', function() {
    return {
        restrict: "A",
        link: function (scope, elem, attr) {
            CKEDITOR.replace(elem[0], {
                uiColor: '#CCEAEE',
                allowedContent: true,
                enterMode : CKEDITOR.ENTER_BR,
                shiftEnterMode: CKEDITOR.ENTER_P,
                autoParagraph: false,
                filebrowserBrowseUrl: '/sysadmin/files/ckeditor',
                filebrowserImageUploadUrl: '/admin/file_upload',
                extraPlugins: 'uploadimage',
                uploadUrl: '/admin/file_upload'
            });
        }
    };
});

angular.module('myApp').directive('selectize', function() {
    return {
        link: function(scope, element, attr) {
            jQuery(element[0]).selectize({
                create: true,
                sortField: 'text'
            }).on('change', function() {
                $(this).valid();
            });
        }
    };
});
<|endoftext|>


<commit_before>'use strict';

const log = require("../../net2/logger")('httpd');

const express = require('express');
const https = require('https');
const forge = require('node-forge');
const qs = require('querystring');
const intel = require('./intel.js');

const port = 80;
const httpsPort = 443;
const app = express();
const enableHttps = false;
const enableRedis = true;

const promise = require('bluebird');
const redis = require('redis');
const client = redis.createClient();
promise.promisifyAll(redis.RedisClient.prototype);

const viewsPath = 'firewalla_views';

app.engine('pug', require('pug').__express);
app.set('views', './firewalla_views');
app.set('view engine', 'pug');

let router = express.Router();

router.all('/green', async (req, res) => {
  const hostname = req.hostname;
  const url = qs.unescape(req.query.url);
  const ip = req.ip;
  const method = req.method;

  log.info("Got a request in porn views");

  res.render('green', {hostname, url, ip, method});
})

app.use(`/${viewsPath}`, router);

app.use('*', async (req, res) => {
  log.info("Got a request in *");

  if (!req.originalUrl.includes(viewsPath)) {
    let cat = await intel.check(req.hostname);

    log.info(`${req.hostname} 's category is ${cat}`);

    switch(cat) {
      case 'porn':
        isPorn(req, res);
        break;
      case 'ad':
        isAd(req, res);
        break;
      default:
        res.status(200).send().end();
    }
  }
});

function isPorn(req, res) {
  res.status(303).location(`/${viewsPath}/green?${qs.stringify({url: req.originalUrl})}`).send().end();
  if (enableRedis) {
    client.hincrbyAsync('block:stats', 'porn', 1).then(value => {
      log.info(`Total porn blocked: ${value}`);
    });
  }
}

function isAd(req, res) {
  res.status(200).send().end();
  if (enableRedis) {
    client.hincrbyAsync('block:stats', 'ad', 1).then(value => {
      log.info(`Total ad blocked: ${value}`);
    });
  }
}

app.listen(port, () => log.info(`Httpd listening on port ${port}!`));

if (enableHttps) {
  const httpsOptions = genHttpsOptions();
  https.createServer(httpsOptions, app).listen(httpsPort, () => console.log(`Httpd listening on port ${httpsPort}!`));
}

function genHttpsOptions() {
  // generate a keypair and create an X.509v3 certificate
  const pki = forge.pki;
  console.log('Generating 1024-bit key-pair...');
  const keys = pki.rsa.generateKeyPair(1024);
  console.log('Key-pair created.');

  console.log('Creating self-signed certificate...');
  const cert = pki.createCertificate();
  cert.publicKey = keys.publicKey;
  cert.serialNumber = '01';
  cert.validity.notBefore = new Date();
  cert.validity.notAfter = new Date();
  cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
  let attrs = [{
    name: 'commonName',
    value: 'blackhole.firewalla.com'
  }, {
    name: 'countryName',
    value: 'US'
  }, {
    shortName: 'ST',
    value: 'New York'
  }, {
    name: 'localityName',
    value: 'Brooklyn'
  }, {
    name: 'organizationName',
    value: 'BLACKHOLE'
  }, {
    shortName: 'OU',
    value: 'BLACKHOLE'
  }];
  cert.setSubject(attrs);
  cert.setIssuer(attrs);
  cert.setExtensions([{
    name: 'basicConstraints',
    cA: true/*,
    pathLenConstraint: 4*/
  }, {
    name: 'keyUsage',
    keyCertSign: true,
    digitalSignature: true,
    nonRepudiation: true,
    keyEncipherment: true,
    dataEncipherment: true
  }, {
    name: 'extKeyUsage',
    serverAuth: true,
    clientAuth: true,
    codeSigning: true,
    emailProtection: true,
    timeStamping: true
  }, {
    name: 'nsCertType',
    client: true,
    server: true,
    email: true,
    objsign: true,
    sslCA: true,
    emailCA: true,
    objCA: true
  }]);
  cert.sign(keys.privateKey);
  console.log('Certificate created.');

  return {
    key: pki.privateKeyToPem(keys.privateKey),
    cert: pki.certificateToPem(cert)
  };
}
<commit_msg>structure app into class<commit_after>'use strict';

const log = require("../../net2/logger")('httpd');

const express = require('express');
const https = require('https');
const forge = require('node-forge');
const qs = require('querystring');
const intel = require('./intel.js');

const port = 80;
const httpsPort = 443;
const enableHttps = false;
const enableRedis = true;

const promise = require('bluebird');
const redis = require('redis');
const client = redis.createClient();
promise.promisifyAll(redis.RedisClient.prototype);

const viewsPath = 'firewalla_views';

class App {
  constructor() {
    this.app = express();
    this.app.engine('pug', require('pug').__express);
    this.app.set('views', './firewalla_views');
    this.app.set('view engine', 'pug');
    this.routes();
  }

  routes() {
    this.router = express.Router();
    this.router.all('/green', async (req, res) => {
      const hostname = req.hostname;
      const url = qs.unescape(req.query.url);
      const ip = req.ip;
      const method = req.method;

      log.info("Got a request in porn views");

      res.render('green', {hostname, url, ip, method});
    })

    this.app.use(`/${viewsPath}`, this.router);

    this.app.use('*', async (req, res) => {
      log.info("Got a request in *");

      if (!req.originalUrl.includes(viewsPath)) {
        let cat = await intel.check(req.hostname);

        log.info(`${req.hostname} 's category is ${cat}`);

        switch(cat) {
          case 'porn':
            this.isPorn(req, res);
            break;
          case 'ad':
            this.isAd(req, res);
            break;
          default:
            res.status(200).send().end();
        }
      }
    });
  }

  start() {
    this.app.listen(port, () => log.info(`Httpd listening on port ${port}!`));

    if (enableHttps) {
      const httpsOptions = this.genHttpsOptions();
      https.createServer(httpsOptions, this.app).listen(httpsPort, () => log.info(`Httpd listening on port ${httpsPort}!`));
    }
  }

  isPorn(req, res) {
    res.status(303).location(`/${viewsPath}/green?${qs.stringify({url: req.originalUrl})}`).send().end();
    if (enableRedis) {
      client.hincrbyAsync('block:stats', 'porn', 1).then(value => {
        log.info(`Total porn blocked: ${value}`);
      });
    }
  }

  isAd(req, res) {
    res.status(200).send().end();
    if (enableRedis) {
      client.hincrbyAsync('block:stats', 'ad', 1).then(value => {
        log.info(`Total ad blocked: ${value}`);
      });
    }
  }

  genHttpsOptions() {
    // generate a keypair and create an X.509v3 certificate
    const pki = forge.pki;
    console.log('Generating 1024-bit key-pair...');
    const keys = pki.rsa.generateKeyPair(1024);
    console.log('Key-pair created.');

    console.log('Creating self-signed certificate...');
    const cert = pki.createCertificate();
    cert.publicKey = keys.publicKey;
    cert.serialNumber = '01';
    cert.validity.notBefore = new Date();
    cert.validity.notAfter = new Date();
    cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
    let attrs = [{
      name: 'commonName',
      value: 'blackhole.firewalla.com'
    }, {
      name: 'countryName',
      value: 'US'
    }, {
      shortName: 'ST',
      value: 'New York'
    }, {
      name: 'localityName',
      value: 'Brooklyn'
    }, {
      name: 'organizationName',
      value: 'BLACKHOLE'
    }, {
      shortName: 'OU',
      value: 'BLACKHOLE'
    }];
    cert.setSubject(attrs);
    cert.setIssuer(attrs);
    cert.setExtensions([{
      name: 'basicConstraints',
      cA: true/*,
    pathLenConstraint: 4*/
    }, {
      name: 'keyUsage',
      keyCertSign: true,
      digitalSignature: true,
      nonRepudiation: true,
      keyEncipherment: true,
      dataEncipherment: true
    }, {
      name: 'extKeyUsage',
      serverAuth: true,
      clientAuth: true,
      codeSigning: true,
      emailProtection: true,
      timeStamping: true
    }, {
      name: 'nsCertType',
      client: true,
      server: true,
      email: true,
      objsign: true,
      sslCA: true,
      emailCA: true,
      objCA: true
    }]);
    cert.sign(keys.privateKey);
    log.info('Certificate created.');

    return {
      key: pki.privateKeyToPem(keys.privateKey),
      cert: pki.certificateToPem(cert)
    };
  }
}

new App().start();<|endoftext|>


<commit_before>var gzipSize = require('gzip-size');

/**
 * Analyzer class
 * @param {Array} rules
 * @param {Array} selectors
 * @param {Array} declarations
 * @param {String} cssString
 * @param {Number} cssSize
 * @param {Object} options
 * @constructor
 */
function Analyzer(rules, selectors, declarations, cssString, cssSize, options) {

  // array of rule
  // referenced in analyzeRules
  this.rules = rules;

  // array of css selector
  // referenced in analyzeSelectors
  this.selectors = selectors;

  // array of css declaration
  // referenced in analyzeDeclarations
  this.declarations = declarations;

  // all of css string
  this.cssString = cssString;

  // size of css
  this.cssSize = cssSize;

  // result options
  this.options = options;
}

/**
 * Analyze rules
 * @returns {
 *   {Number} totalCssDeclarations,
 *   {Array} cssDeclarations
 * }
 */
Analyzer.prototype.analyzeRules = function () {

  // object to return
  var result = {
    totalCssDeclarations: 0,
    cssDeclarations: []
  };

  // analyze rules
  this.rules.forEach(function (rule) {
    if (Array.isArray(rule.declarations)) {
      result.cssDeclarations.push({
        selector: rule.selectors,
        count: rule.declarations.length
      });
    }
  });

  // sort by css declaration count
  result.cssDeclarations.sort(function decreasingOrder(a, b) {
    return b.count - a.count;
  });
  result.cssDeclarations.forEach(function (obj) {
    result.totalCssDeclarations += obj.count;
  });

  return result;
};

/**
 * Analyze selectors
 * @returns {
 *   {Number} idSelectors,
 *   {Number} universalSelectors,
 *   {Number} unqualifiedAttributeSelectors,
 *   {Number} javascriptSpecificSelectors,
 *   {Number} totalIdentifiers,
 *   {Array} identifiers
 * }
 */
Analyzer.prototype.analyzeSelectors = function () {

  // object to return
  var result = {
    idSelectors: 0,
    universalSelectors: 0,
    unqualifiedAttributeSelectors: 0,
    javascriptSpecificSelectors: 0,
    userSpecifiedSelectors: 0,
    totalIdentifiers: 0,
    identifiers: []
  };

  // specified JavaScript hook selector
  var regexpJs = new RegExp(this.options.javascriptSpecificSelectors);
  // specified user-specified hook selector
  var regexpUser = new RegExp(this.options.userSpecifiedSelectors);

  // analyze selectors
  this.selectors.forEach(function (selector) {

    // if it contains # and dose not contain # in attribute selector
    if (selector.indexOf('#') > -1) {
      var id = selector.replace(/\[.+\]/g, '');
      if (id.indexOf('#') > -1) {
        result.idSelectors += 1;
      }
    }

    // if it contains * and dose not contain * in attribute selector
    if (selector.indexOf('*') > -1) {
      var universal = selector.replace(/\[.+\]/g, '');
      if (universal.indexOf('*') > -1) {
        result.universalSelectors += 1;
      }
    }

    // if it is unqualified attribute selector
    if (selector.trim().match(/\[.+\]$/g)) {
      result.unqualifiedAttributeSelectors += 1;
    }

    // if it is for JavaScript hook
    if (regexpJs.test(selector.trim())) {
      result.javascriptSpecificSelectors += 1;
    }

    // if it is for user-specified hook
    if (regexpUser.test(selector.trim())) {
      result.userSpecifiedSelectors += 1;
    }

    // add selector for statistics
    var trimmedSelector = selector.replace(/\s?([\>|\+|\~])\s?/g, '$1');
    trimmedSelector = trimmedSelector.replace(/\s+/g, ' ');
    var count = trimmedSelector.split(/\s|\>|\+|\~|\:|[\w\]]\.|[\w\]]\#|\[/).length;
    result.identifiers.push({
      selector: selector,
      count: count
    });
  });
  result.identifiers.forEach(function (obj) {
    result.totalIdentifiers += obj.count;
  });

  // sort by chained selector count
  result.identifiers.sort(function decreasingOrder(a, b) {
    return b.count - a.count;
  });

  return result;
};

/**
 * Analyze declarations
 * @returns {
 *   {String} dataUriSize,
 *   {Number} importantKeywords,
 *   {Number} floatProperties,
 *   {Array} uniqueFontSizes,
 *   {Array} uniqueFontFamilies
 *   {Array} uniqueColors,
 *   {Object} properties
 * }
 */
Analyzer.prototype.analyzeDeclarations = function () {

  // object to return
  var result = {
    dataUriSize: '',
    importantKeywords: 0,
    floatProperties: 0,
    uniqueFontSizes: [],
    uniqueFontFamilies: [],
    uniqueColors: [],
    uniqueBackgroundImages: [],
    properties: {}
  };

  // analyze declarations
  this.declarations.forEach(function (declaration) {

    // if it contains DataURI
    if (declaration.value.indexOf('data:image') > -1) {
      result.dataUriSize += declaration.value.match(/data\:image\/[A-Za-z0-9;,\+\=\/]+/);
    }

    // if it contains !important keyword
    if (declaration.value.indexOf('!important') > -1) {
      result.importantKeywords += 1;
    }

    // if it contains float
    if (declaration.property.indexOf('float') > -1) {
      result.floatProperties += 1;
    }

    // if it contains font-family
    if (declaration.property.indexOf('font-family') > -1) {
      result.uniqueFontFamilies.push(declaration.value.replace(/(\!important)/g, '').trim());
    }

    // if it contains font-size
    if (declaration.property.indexOf('font-size') > -1) {
      result.uniqueFontSizes.push(declaration.value.replace(/\!important/, '').trim());
    }

    // if it contains colors
    if (declaration.property.match(/^color$/)) {
      var color = declaration.value.replace(/\!important/, '');
      color = color.toUpperCase().trim();
      result.uniqueColors.push(color);
    }

    // if it contains background-image url()
    if (declaration.property.indexOf('background') > -1 && declaration.value.indexOf('url') > -1) {
      var paths = declaration.value.match(/url\(([^)]+)\)/g);
      if (paths) {
        paths.forEach(function (path) {
          result.uniqueBackgroundImages.push(path.replace(/^url|[\(\)\'\"]/g, ''));
        });
      }
    }

    // property statistics
    if (result.properties[declaration.property]) {
      result.properties[declaration.property] += 1;
    } else {
      result.properties[declaration.property] = 1;
    }
  });

  // Return byte size.
  result.dataUriSize = Buffer.byteLength(result.dataUriSize, 'utf8');

  // Sort `font-family` property.
  result.uniqueFontFamilies = result.uniqueFontFamilies.filter(function (fontFamily, index, array) {
    return array.indexOf(fontFamily) === index;
  }).sort();

  // Sort `font-size` property.
  result.uniqueFontSizes = result.uniqueFontSizes.filter(function (fontSize, index, array) {
    return array.indexOf(fontSize) === index;
  }).sort(function (a, b) {
    return Number(a.replace(/[^0-9\.]/g, '')) - Number(b.replace(/[^0-9\.]/g, ''));
  });
  // Categorize per unit and concat
  var uniqueFontSizes = {};
  result.uniqueFontSizes.forEach(function (value) {
    var unit = value.replace(/[0-9\.]/g, '');
    if (!uniqueFontSizes[unit]) {
      uniqueFontSizes[unit] = [];
    }
    uniqueFontSizes[unit].push(value);
  });
  result.uniqueFontSizes = [];
  Object.keys(uniqueFontSizes).forEach(function (key) {
    uniqueFontSizes[key].forEach(function (value) {
      result.uniqueFontSizes.push(value);
    });
  });

  // Sort `color` property.
  var trimmedColors = result.uniqueColors.filter(function (uniqueColor) {
    return uniqueColor !== 'TRANSPARENT' && uniqueColor !== 'INHERIT';
  });

  var formattedColors = trimmedColors.map(function (color) {
    var formattedColor = color;
    if (/^#([0-9A-F]){3}$/.test(formattedColor)) {
      formattedColor = color.replace(/^#(\w)(\w)(\w)$/, '#$1$1$2$2$3$3');
    }
    return formattedColor;
  });
  result.uniqueColors = formattedColors.filter(function (formattedColor, index, array) {
    return array.indexOf(formattedColor) === index;
  }).sort();

  // if it contains background-image url()
  result.uniqueBackgroundImages = result.uniqueBackgroundImages.filter(function (backgroundImage, index, array) {
    return array.indexOf(backgroundImage) === index;
  }).sort();

  // Sort properties count.
  var propertiesCount = [];
  Object.keys(result.properties).forEach(function (key) {
    propertiesCount.push({
      property: key,
      count: result.properties[key]
    });
  });

  // sort by property count
  result.properties = propertiesCount.sort(function decreasingOrder(a, b) {
    return b.count - a.count;
  });

  return result;
};

/**
 * Analyze css from rules, selectors, declarations
 * @returns {
 *   {Number} stylesheets,
 *   {Number} size,
 *   {Number} dataUriSize,
 *   {Number} ratioOfDataUriSize,
 *   {Number} gzippedSize,
 *   {Number} rules,
 *   {Number} selectors,
 *   {Float}  simplicity,
 *   {Number} averageOfIdentifier,
 *   {Number} mostIdentifier,
 *   {String} mostIdentifierSelector,
 *   {Number} averageOfCohesion,
 *   {Number} lowestCohesion,
 *   {Number} lowestCohesionSelector,
 *   {Number} totalUniqueFontSizes,
 *   {String} uniqueFontSizes,
 *   {Number} totalUniqueFontFamilies,
 *   {String} uniqueFontSizes,
 *   {Number} totalUniqueColors,
 *   {String} uniqueColors,
 *   {Number} totalUniqueFontFamilies
 *   {String} uniqueFontFamilies,
 *   {Number} totalUniqueBackgroundImages
 *   {String} uniqueBackgroundImages,
 *   {Number} idSelectors,
 *   {Number} universalSelectors,
 *   {Number} unqualifiedAttributeSelectors,
 *   {Number} javascriptSpecificSelectors,
 *   {Number} importantKeywords,
 *   {Number} floatProperties,
 *   {Number} propertiesCount
 * }
 */
Analyzer.prototype.analyze = function () {

  // get analytics
  var ruleAnalysis = this.analyzeRules();
  var selectorAnalysis = this.analyzeSelectors();
  var declarationAnalysis = this.analyzeDeclarations();

  var analysis = {};
  if (this.options.size) {
    analysis.size = this.cssSize;
  }
  if (this.options.dataUriSize) {
    analysis.dataUriSize = declarationAnalysis.dataUriSize;
  }
  if (this.options.dataUriSize) {
    analysis.ratioOfDataUriSize = declarationAnalysis.dataUriSize / this.cssSize;
  }
  if (this.options.gzippedSize) {
    analysis.gzippedSize = gzipSize.sync(this.cssString);
  }
  if (this.options.rules) {
    analysis.rules = this.rules.length;
  }
  if (this.options.selectors) {
    analysis.selectors = this.selectors.length;
  }
  if (this.options.declarations) {
    analysis.declarations = this.declarations.length;
  }
  if (this.options.simplicity) {
    var simplicity = analysis.rules / this.selectors.length;
    analysis.simplicity = isNaN(simplicity) ? 0 : simplicity;
  }
  if (this.selectors.length && this.options.averageOfIdentifier) {
    analysis.averageOfIdentifier = selectorAnalysis.totalIdentifiers / this.selectors.length;
  }
  // Most Identifier
  var mostIdentifier = selectorAnalysis.identifiers.shift();
  if (mostIdentifier && this.options.mostIdentifier) {
    analysis.mostIdentifier = mostIdentifier.count;
  }
  if (mostIdentifier && this.options.mostIdentifierSelector) {
    analysis.mostIdentifierSelector = mostIdentifier.selector;
  }
  if (this.rules.length && this.options.averageOfCohesion) {
    analysis.averageOfCohesion = ruleAnalysis.totalCssDeclarations / this.rules.length;
  }
  var lowestDefinition = ruleAnalysis.cssDeclarations.shift();
  if (lowestDefinition && this.options.lowestCohesion) {
    analysis.lowestCohesion = lowestDefinition.count;
  }
  if (lowestDefinition && this.options.lowestCohesionSelector) {
    analysis.lowestCohesionSelector = lowestDefinition.selector;
  }
  if (this.options.totalUniqueFontSizes) {
    analysis.totalUniqueFontSizes = declarationAnalysis.uniqueFontSizes.length;
  }
  if (this.options.uniqueFontSizes) {
    analysis.uniqueFontSizes = declarationAnalysis.uniqueFontSizes;
  }
  if (this.options.totalUniqueFontFamilies) {
    analysis.totalUniqueFontFamilies = declarationAnalysis.uniqueFontFamilies.length;
  }
  if (this.options.uniqueFontFamilies) {
    analysis.uniqueFontFamilies = declarationAnalysis.uniqueFontFamilies;
  }
  if (this.options.totalUniqueColors) {
    analysis.totalUniqueColors = declarationAnalysis.uniqueColors.length;
  }
  if (this.options.uniqueColors) {
    analysis.uniqueColors = declarationAnalysis.uniqueColors;
  }
  if (this.options.totalUniqueBackgroundImages) {
    analysis.totalUniqueBackgroundImages = declarationAnalysis.uniqueBackgroundImages.length;
  }
  if (this.options.uniqueBackgroundImages) {
    analysis.uniqueBackgroundImages = declarationAnalysis.uniqueBackgroundImages;
  }
  if (this.options.idSelectors) {
    analysis.idSelectors = selectorAnalysis.idSelectors;
  }
  if (this.options.universalSelectors) {
    analysis.universalSelectors = selectorAnalysis.universalSelectors;
  }
  if (this.options.unqualifiedAttributeSelectors) {
    analysis.unqualifiedAttributeSelectors = selectorAnalysis.unqualifiedAttributeSelectors;
  }
  if (this.options.javascriptSpecificSelectors) {
    analysis.javascriptSpecificSelectors = selectorAnalysis.javascriptSpecificSelectors;
  }
  if (this.options.userSpecifiedSelectors) {
    analysis.userSpecifiedSelectors = selectorAnalysis.userSpecifiedSelectors;
  }
  if (this.options.importantKeywords) {
    analysis.importantKeywords = declarationAnalysis.importantKeywords;
  }
  if (this.options.floatProperties) {
    analysis.floatProperties = declarationAnalysis.floatProperties;
  }
  if (this.options.propertiesCount) {
    analysis.propertiesCount = declarationAnalysis.properties.slice(0, this.options.propertiesCount);
  }
  return analysis;
};

module.exports = Analyzer;
<commit_msg>lib/analyzer.js<commit_after>const gzipSize = require('gzip-size');

class Analyzer {
  /**
   * @param {Array} rules
   * @param {Array} selectors
   * @param {Array} declarations
   * @param {String} cssString
   * @param {Number} cssSize
   * @param {Object} options
   */
  constructor(rules, selectors, declarations, cssString, cssSize, options) {
    // array of rule
    // referenced in analyzeRules
    this.rules = rules;

    // array of css selector
    // referenced in analyzeSelectors
    this.selectors = selectors;

    // array of css declaration
    // referenced in analyzeDeclarations
    this.declarations = declarations;

    // all of css string
    this.cssString = cssString;

    // size of css
    this.cssSize = cssSize;

    // result options
    this.options = options;
  }

  /**
   * Analyze rules
   * @returns {
   *   {Number} totalCssDeclarations,
   *   {Array} cssDeclarations
   * }
   */
  analyzeRules() {
    // object to return
    let result = {
      totalCssDeclarations: 0,
      cssDeclarations: []
    };

    // analyze rules
    this.rules.forEach(function (rule) {
      if (Array.isArray(rule.declarations)) {
        result.cssDeclarations.push({
          selector: rule.selectors,
          count: rule.declarations.length
        });
      }
    });

    // sort by css declaration count
    result.cssDeclarations.sort(function decreasingOrder(a, b) {
      return b.count - a.count;
    });
    result.cssDeclarations.forEach(function (obj) {
      result.totalCssDeclarations += obj.count;
    });

    return result;
  }

  /**
   * Analyze selectors
   * @returns {
   *   {Number} idSelectors,
   *   {Number} universalSelectors,
   *   {Number} unqualifiedAttributeSelectors,
   *   {Number} javascriptSpecificSelectors,
   *   {Number} totalIdentifiers,
   *   {Array} identifiers
   * }
   */
  analyzeSelectors() {
    // object to return
    let result = {
      idSelectors: 0,
      universalSelectors: 0,
      unqualifiedAttributeSelectors: 0,
      javascriptSpecificSelectors: 0,
      userSpecifiedSelectors: 0,
      totalIdentifiers: 0,
      identifiers: []
    };

    // specified JavaScript hook selector
    let regexpJs = new RegExp(this.options.javascriptSpecificSelectors);
    // specified user-specified hook selector
    let regexpUser = new RegExp(this.options.userSpecifiedSelectors);

    // analyze selectors
    this.selectors.forEach(function (selector) {

      // if it contains # and dose not contain # in attribute selector
      if (selector.indexOf('#') > -1) {
        let id = selector.replace(/\[.+\]/g, '');
        if (id.indexOf('#') > -1) {
          result.idSelectors += 1;
        }
      }

      // if it contains * and dose not contain * in attribute selector
      if (selector.indexOf('*') > -1) {
        let universal = selector.replace(/\[.+\]/g, '');
        if (universal.indexOf('*') > -1) {
          result.universalSelectors += 1;
        }
      }

      // if it is unqualified attribute selector
      if (selector.trim().match(/\[.+\]$/g)) {
        result.unqualifiedAttributeSelectors += 1;
      }

      // if it is for JavaScript hook
      if (regexpJs.test(selector.trim())) {
        result.javascriptSpecificSelectors += 1;
      }

      // if it is for user-specified hook
      if (regexpUser.test(selector.trim())) {
        result.userSpecifiedSelectors += 1;
      }

      // add selector for statistics
      let trimmedSelector = selector.replace(/\s?([\>|\+|\~])\s?/g, '$1');
      trimmedSelector = trimmedSelector.replace(/\s+/g, ' ');
      let count = trimmedSelector.split(/\s|\>|\+|\~|\:|[\w\]]\.|[\w\]]\#|\[/).length;
      result.identifiers.push({
        selector: selector,
        count: count
      });
    });
    result.identifiers.forEach(function (obj) {
      result.totalIdentifiers += obj.count;
    });

    // sort by chained selector count
    result.identifiers.sort(function decreasingOrder(a, b) {
      return b.count - a.count;
    });

    return result;
  }

  /**
   * Analyze declarations
   * @returns {
   *   {String} dataUriSize,
   *   {Number} importantKeywords,
   *   {Number} floatProperties,
   *   {Array} uniqueFontSizes,
   *   {Array} uniqueFontFamilies
   *   {Array} uniqueColors,
   *   {Object} properties
   * }
   */
  analyzeDeclarations() {
    // object to return
    let result = {
      dataUriSize: '',
      importantKeywords: 0,
      floatProperties: 0,
      uniqueFontSizes: [],
      uniqueFontFamilies: [],
      uniqueColors: [],
      uniqueBackgroundImages: [],
      properties: {}
    };

    // analyze declarations
    this.declarations.forEach(function (declaration) {

      // if it contains DataURI
      if (declaration.value.indexOf('data:image') > -1) {
        result.dataUriSize += declaration.value.match(/data\:image\/[A-Za-z0-9;,\+\=\/]+/);
      }

      // if it contains !important keyword
      if (declaration.value.indexOf('!important') > -1) {
        result.importantKeywords += 1;
      }

      // if it contains float
      if (declaration.property.indexOf('float') > -1) {
        result.floatProperties += 1;
      }

      // if it contains font-family
      if (declaration.property.indexOf('font-family') > -1) {
        result.uniqueFontFamilies.push(declaration.value.replace(/(\!important)/g, '').trim());
      }

      // if it contains font-size
      if (declaration.property.indexOf('font-size') > -1) {
        result.uniqueFontSizes.push(declaration.value.replace(/\!important/, '').trim());
      }

      // if it contains colors
      if (declaration.property.match(/^color$/)) {
        let color = declaration.value.replace(/\!important/, '');
        color = color.toUpperCase().trim();
        result.uniqueColors.push(color);
      }

      // if it contains background-image url()
      if (declaration.property.indexOf('background') > -1 && declaration.value.indexOf('url') > -1) {
        let paths = declaration.value.match(/url\(([^)]+)\)/g);
        if (paths) {
          paths.forEach(function (path) {
            result.uniqueBackgroundImages.push(path.replace(/^url|[\(\)\'\"]/g, ''));
          });
        }
      }

      // property statistics
      if (result.properties[declaration.property]) {
        result.properties[declaration.property] += 1;
      } else {
        result.properties[declaration.property] = 1;
      }
    });

    // Return byte size.
    result.dataUriSize = Buffer.byteLength(result.dataUriSize, 'utf8');

    // Sort `font-family` property.
    result.uniqueFontFamilies = result.uniqueFontFamilies.filter(function (fontFamily, index, array) {
      return array.indexOf(fontFamily) === index;
    }).sort();

    // Sort `font-size` property.
    result.uniqueFontSizes = result.uniqueFontSizes.filter(function (fontSize, index, array) {
      return array.indexOf(fontSize) === index;
    }).sort(function (a, b) {
      return Number(a.replace(/[^0-9\.]/g, '')) - Number(b.replace(/[^0-9\.]/g, ''));
    });
    // Categorize per unit and concat
    let uniqueFontSizes = {};
    result.uniqueFontSizes.forEach(function (value) {
      let unit = value.replace(/[0-9\.]/g, '');
      if (!uniqueFontSizes[unit]) {
        uniqueFontSizes[unit] = [];
      }
      uniqueFontSizes[unit].push(value);
    });
    result.uniqueFontSizes = [];
    Object.keys(uniqueFontSizes).forEach(function (key) {
      uniqueFontSizes[key].forEach(function (value) {
        result.uniqueFontSizes.push(value);
      });
    });

    // Sort `color` property.
    let trimmedColors = result.uniqueColors.filter(function (uniqueColor) {
      return uniqueColor !== 'TRANSPARENT' && uniqueColor !== 'INHERIT';
    });

    let formattedColors = trimmedColors.map(function (color) {
      let formattedColor = color;
      if (/^#([0-9A-F]){3}$/.test(formattedColor)) {
        formattedColor = color.replace(/^#(\w)(\w)(\w)$/, '#$1$1$2$2$3$3');
      }
      return formattedColor;
    });
    result.uniqueColors = formattedColors.filter(function (formattedColor, index, array) {
      return array.indexOf(formattedColor) === index;
    }).sort();

    // if it contains background-image url()
    result.uniqueBackgroundImages = result.uniqueBackgroundImages.filter(function (backgroundImage, index, array) {
      return array.indexOf(backgroundImage) === index;
    }).sort();

    // Sort properties count.
    let propertiesCount = [];
    Object.keys(result.properties).forEach(function (key) {
      propertiesCount.push({
        property: key,
        count: result.properties[key]
      });
    });

    // sort by property count
    result.properties = propertiesCount.sort(function decreasingOrder(a, b) {
      return b.count - a.count;
    });

    return result;
  }

  /**
   * Analyze css from rules, selectors, declarations
   * @returns {
   *   {Number} stylesheets,
   *   {Number} size,
   *   {Number} dataUriSize,
   *   {Number} ratioOfDataUriSize,
   *   {Number} gzippedSize,
   *   {Number} rules,
   *   {Number} selectors,
   *   {Float}  simplicity,
   *   {Number} averageOfIdentifier,
   *   {Number} mostIdentifier,
   *   {String} mostIdentifierSelector,
   *   {Number} averageOfCohesion,
   *   {Number} lowestCohesion,
   *   {Number} lowestCohesionSelector,
   *   {Number} totalUniqueFontSizes,
   *   {String} uniqueFontSizes,
   *   {Number} totalUniqueFontFamilies,
   *   {String} uniqueFontSizes,
   *   {Number} totalUniqueColors,
   *   {String} uniqueColors,
   *   {Number} totalUniqueFontFamilies
   *   {String} uniqueFontFamilies,
   *   {Number} totalUniqueBackgroundImages
   *   {String} uniqueBackgroundImages,
   *   {Number} idSelectors,
   *   {Number} universalSelectors,
   *   {Number} unqualifiedAttributeSelectors,
   *   {Number} javascriptSpecificSelectors,
   *   {Number} importantKeywords,
   *   {Number} floatProperties,
   *   {Number} propertiesCount
   * }
   */
  analyze() {
    // get analytics
    let ruleAnalysis = this.analyzeRules();
    let selectorAnalysis = this.analyzeSelectors();
    let declarationAnalysis = this.analyzeDeclarations();

    let analysis = {};
    if (this.options.size) {
      analysis.size = this.cssSize;
    }
    if (this.options.dataUriSize) {
      analysis.dataUriSize = declarationAnalysis.dataUriSize;
    }
    if (this.options.dataUriSize) {
      analysis.ratioOfDataUriSize = declarationAnalysis.dataUriSize / this.cssSize;
    }
    if (this.options.gzippedSize) {
      analysis.gzippedSize = gzipSize.sync(this.cssString);
    }
    if (this.options.rules) {
      analysis.rules = this.rules.length;
    }
    if (this.options.selectors) {
      analysis.selectors = this.selectors.length;
    }
    if (this.options.declarations) {
      analysis.declarations = this.declarations.length;
    }
    if (this.options.simplicity) {
      let simplicity = analysis.rules / this.selectors.length;
      analysis.simplicity = isNaN(simplicity) ? 0 : simplicity;
    }
    if (this.selectors.length && this.options.averageOfIdentifier) {
      analysis.averageOfIdentifier = selectorAnalysis.totalIdentifiers / this.selectors.length;
    }
    // Most Identifier
    let mostIdentifier = selectorAnalysis.identifiers.shift();
    if (mostIdentifier && this.options.mostIdentifier) {
      analysis.mostIdentifier = mostIdentifier.count;
    }
    if (mostIdentifier && this.options.mostIdentifierSelector) {
      analysis.mostIdentifierSelector = mostIdentifier.selector;
    }
    if (this.rules.length && this.options.averageOfCohesion) {
      analysis.averageOfCohesion = ruleAnalysis.totalCssDeclarations / this.rules.length;
    }
    let lowestDefinition = ruleAnalysis.cssDeclarations.shift();
    if (lowestDefinition && this.options.lowestCohesion) {
      analysis.lowestCohesion = lowestDefinition.count;
    }
    if (lowestDefinition && this.options.lowestCohesionSelector) {
      analysis.lowestCohesionSelector = lowestDefinition.selector;
    }
    if (this.options.totalUniqueFontSizes) {
      analysis.totalUniqueFontSizes = declarationAnalysis.uniqueFontSizes.length;
    }
    if (this.options.uniqueFontSizes) {
      analysis.uniqueFontSizes = declarationAnalysis.uniqueFontSizes;
    }
    if (this.options.totalUniqueFontFamilies) {
      analysis.totalUniqueFontFamilies = declarationAnalysis.uniqueFontFamilies.length;
    }
    if (this.options.uniqueFontFamilies) {
      analysis.uniqueFontFamilies = declarationAnalysis.uniqueFontFamilies;
    }
    if (this.options.totalUniqueColors) {
      analysis.totalUniqueColors = declarationAnalysis.uniqueColors.length;
    }
    if (this.options.uniqueColors) {
      analysis.uniqueColors = declarationAnalysis.uniqueColors;
    }
    if (this.options.totalUniqueBackgroundImages) {
      analysis.totalUniqueBackgroundImages = declarationAnalysis.uniqueBackgroundImages.length;
    }
    if (this.options.uniqueBackgroundImages) {
      analysis.uniqueBackgroundImages = declarationAnalysis.uniqueBackgroundImages;
    }
    if (this.options.idSelectors) {
      analysis.idSelectors = selectorAnalysis.idSelectors;
    }
    if (this.options.universalSelectors) {
      analysis.universalSelectors = selectorAnalysis.universalSelectors;
    }
    if (this.options.unqualifiedAttributeSelectors) {
      analysis.unqualifiedAttributeSelectors = selectorAnalysis.unqualifiedAttributeSelectors;
    }
    if (this.options.javascriptSpecificSelectors) {
      analysis.javascriptSpecificSelectors = selectorAnalysis.javascriptSpecificSelectors;
    }
    if (this.options.userSpecifiedSelectors) {
      analysis.userSpecifiedSelectors = selectorAnalysis.userSpecifiedSelectors;
    }
    if (this.options.importantKeywords) {
      analysis.importantKeywords = declarationAnalysis.importantKeywords;
    }
    if (this.options.floatProperties) {
      analysis.floatProperties = declarationAnalysis.floatProperties;
    }
    if (this.options.propertiesCount) {
      analysis.propertiesCount = declarationAnalysis.properties.slice(0, this.options.propertiesCount);
    }
    return analysis;
  }
}

module.exports = Analyzer;
<|endoftext|>


<commit_before>// @flow

import type { FragmentSetting } from 'types/pattern'
import Fragments from 'components/seq/fragments/index'

const { Stairs, Transpose, Repeat, Limit, Stretch, Scale } = Fragments

export const seqFragments: Array<FragmentSetting> = [
/*
  {
    class: Stairs,
    props: { notes: 5 },
  },
  {
    class: Transpose,
    props: { transpose: -31 },
  },
  {
    class: Repeat,
    props: { count: 2 },
  },
*/
  {
    class: Repeat,
    props: { count: 4 },
  },
  {
    class: Stairs,
    props: { notes: 1 },
  },
  {
    class: Scale,
    props: { root: 2 },
  },
  {
    class: Limit,
    props: { top: 100, bottom: 10 },
  },
]

export const drumsFragments: Array<FragmentSetting> = [
  {
    class: Stretch,
    props: { count: 1.0 },
  },
]

export const padFragments: Array<FragmentSetting> = [
  {
    class: Repeat,
    props: { count: 4 },
  },
/*
  {
    class: Stairs,
    props: { notes: 5 },
  },
*/
]

export default {}
<commit_msg>modify presets<commit_after>// @flow

import type { FragmentSetting } from 'types/pattern'
import Fragments from 'components/seq/fragments/index'

const { Stairs, Transpose, Repeat, Limit, Stretch, Scale } = Fragments

export const seqFragments: Array<FragmentSetting> = [
  {
    class: Repeat,
    props: { count: 2 },
  },
  {
    class: Stairs,
    props: { notes: 5 },
  },
  {
    class: Transpose,
    props: { transpose: -31 },
  },
  {
    class: Scale,
    props: { root: 2 },
  },
]

export const drumsFragments: Array<FragmentSetting> = [
  {
    class: Stretch,
    props: { count: 1.0 },
  },
]

export const padFragments: Array<FragmentSetting> = [
  {
    class: Repeat,
    props: { count: 4 },
  },
  {
    class: Scale,
    props: { root: 2 },
  },
]

export default {}
<|endoftext|>


<commit_before>const _ = require('underscore');
const Player = require('./player.js');
const Settings = require('../settings.js');

class Conflict {
    constructor(game, attackingPlayer, defendingPlayer, conflictType = '', conflictRing = '', conflictProvince = null) {
        this.game = game;
        this.attackingPlayer = attackingPlayer;
        this.isSinglePlayer = !defendingPlayer;
        this.defendingPlayer = defendingPlayer || this.singlePlayerDefender();
        this.conflictDeclaredRing = conflictRing;
        this.conflictPassed = false;
        this.conflictType = conflictType;
        this.conflictRing = conflictRing;
        this.conflictProvince = conflictProvince;
        this.conflictTypeSwitched = false;
        this.conflictUnopposed = false;
        this.winnerGoesStraightToNextConflict = false;
        this.elementsToResolve = 1;
        this.elements = [];
        this.attackers = [];
        this.attackerSkill = 0;
        this.attackerSkillModifier = 0;
        this.defenders = [];
        this.defenderSkill = 0;
        this.maxAllowedDefenders = 0;
        this.defenderSkillModifier = 0;
    }

    singlePlayerDefender() {
        let dummyPlayer = new Player('', Settings.getUserWithDefaultsSet({ name: 'Dummy Player' }), false, this.game);
        dummyPlayer.initialise();
        return dummyPlayer;
    }

    resetCards() {
        this.attackingPlayer.resetForConflict();
        this.defendingPlayer.resetForConflict();
        if(this.conflictProvince) {
            this.conflictProvince.inConflict = false;
        }
    }

    initiateConflict() {
        this.attackingPlayer.initiateConflict(this.conflictType);
    }

    addAttackers(attackers) {
        attackers = _.reject(attackers, card => this.isAttacking(card));
        if(attackers.length > 0) {
            this.attackers = this.attackers.concat(attackers);
            this.markAsParticipating(attackers);
            this.calculateSkill();
        }
    }

    addAttacker(attacker) {
        if(this.attackers.includes(attacker)) {
            return;
        }
        this.attackers.push(attacker);
        this.markAsParticipating([attacker]);
        this.calculateSkill();
    }

    addDefenders(defenders) {
        defenders = _.reject(defenders, card => this.isDefending(card));
        if(defenders.length > 0) {
            this.defenders = this.defenders.concat(defenders);
            this.markAsParticipating(defenders);
            this.calculateSkill();
        }
    }

    addDefender(defender) {
        if(this.defenders.includes(defender)) {
            return;
        }
        this.defenders.push(defender);
        this.markAsParticipating([defender]);
        this.calculateSkill();
    }

    moveToConflict(cards) {
        if(!_.isArray(cards)) {
            cards = [cards];
        }
        let events = _.map(cards, card => {
            return {
                name: 'onMoveToConflict',
                params: { conflict: this, card: card },
                handler: card.controller.isAttackingPlayer() ? () => this.addAttacker(card) : () => this.addDefender(card)
            };
        });
        this.game.raiseMultipleEvents(events, {
            name: 'onMoveCharactersToConflict',
            params: { conflict: this, cards: cards }
        });
    }

    sendHome(cards) {
        if(!_.isArray(cards)) {
            cards = [cards];
        }
        let events = _.map(cards, card => {
            return {
                name: 'onSendHome',
                params: { conflict: this, card: card },
                handler: () => this.removeFromConflict(card)
            };
        });
        this.game.raiseMultipleEvents(events, {
            name: 'onSendCharactersHome',
            params: { conflict: this, cards: cards }
        });
    }

    modifyElementsToResolve(amount) {
        this.elementsToResolve += amount;
    }

    hasElement(element) {
        return this.elements.includes(element);
    }

    getElements() {
        return _.uniq(this.elements);
    }

    addElement(element) {
        this.elements.push(element);
    }

    removeElement(element) {
        let index = _.indexOf(this.elements, element);
        if(index > -1) {
            this.elements.splice(index, 1);
        }
    }

    chooseWhetherToResolveRingEffect(player = this.attackingPlayer) {
        this.game.raiseEvent('onResolveRingEffect', { player: player, conflict: this }, () => {
            let elements = this.getElements();
            if(elements.length === 1) {
                this.game.promptWithHandlerMenu(player, {
                    activePromptTitle: 'Do you want to resolve the ' + elements[0] + ' ring?',
                    waitingPromptTitle: 'Waiting for opponent to use decide whether to resolve the conflict ring',
                    source: 'Resolve Ring Effects',
                    choices: ['Yes', 'No'],
                    handlers: [() => this.resolveConflictRing(player), () => this.game.addMessage('{0} chooses not to resolve the {1} ring', player, elements[0])]
                });
            } else {
                this.resolveConflictRing(player);
            }
            return { resolved: true, success: true };
        });
    }

    resolveConflictRing(player = this.attackingPlayer, optional = true) {
        let elements = this.getElements();
        if(elements.length === 0) {
            return;
        }
        if(elements.length === 1) {
            player.resolveRingEffects(elements, optional);
            return;
        }
        if(this.elementsToResolve >= elements.length) {
            if(optional) {
                this.game.promptWithHandlerMenu(player, {
                    activePromptTitle: 'Do you want to resolve all the elements of the conflict ring?',
                    waitingPromptTitle: 'Waiting for opponent to use decide whether to resolve the conflict ring',
                    source: 'Resolve Ring Effects',
                    choices: ['Resolve All Elements', 'Choose Elements to Resolve'],
                    handlers: [() => player.resolveRingEffects(elements, optional), () => this.chooseElementsToResolve(player, optional, this.elementsToResolve, elements)]
                });
                return;
            }
            player.resolveRingEffects(elements, optional);
            return;
        }
        this.chooseElementsToResolve(player, optional, this.elementsToResolve, elements);
    }

    chooseElementsToResolve(player, optional, elementsToResolve, elements, chosenElements = []) {
        if(elements.length === 0 || elementsToResolve === 0) {
            player.resolveRingEffects(chosenElements, optional);
            return;
        }
        let activePromptTitle = 'Choose a ring effect to resolve';
        if(chosenElements.length > 0) {
            activePromptTitle = _.reduce(chosenElements, (string, element) => string + ' ' + element, activePromptTitle + '\nChosen elements:');
        }
        this.game.promptForRingSelect(player, {
            activePromptTitle: activePromptTitle,
            ringCondition: ring => elements.includes(ring.element),
            onSelect: (player, ring) => {
                elementsToResolve--;
                chosenElements.push(ring.element);
                this.chooseElementsToResolve(player, optional, elementsToResolve, _.without(elements, ring.element), chosenElements);
                return true;
            },
            onCancel: () => player.resolveRingEffects(chosenElements, optional)
        });
    }

    switchType() {
        let ring = this.game.rings[this.conflictRing];
        ring.flipConflictType();
        this.conflictType = ring.conflictType;
        this.conflictTypeSwitched = true;
        this.game.reapplyStateDependentEffects();
        this.checkForIllegalParticipants();
    }

    switchElement(element) {
        let oldRing = this.game.rings[this.conflictRing];
        oldRing.contested = false;
        this.elements = _.reject(this.elements, element => element === oldRing.element);
        this.conflictRing = element;
        let newRing = this.game.rings[element];
        this.game.addFate(this.attackingPlayer, newRing.fate);
        newRing.fate = 0;
        newRing.contested = true;
        if(newRing.conflictType !== this.conflictType) {
            newRing.flipConflictType();
        }
        this.elements.push(element);
    }

    checkForIllegalParticipants() {
        _.each(this.attackers, card => {
            if(!card.canParticipateAsAttacker(this.conflictType)) {
                this.removeFromConflict(card);
                card.bowed = true;
            }
        });
        _.each(this.defenders, card => {
            if(!card.canParticipateAsDefender(this.conflictType)) {
                this.removeFromConflict(card);
                card.bowed = true;
            }
        });
    }

    removeFromConflict(card) {
        this.attackers = _.reject(this.attackers, c => c === card);
        this.defenders = _.reject(this.defenders, c => c === card);

        card.inConflict = false;

        this.calculateSkill();
    }

    markAsParticipating(cards) {
        _.each(cards, card => {
            card.inConflict = true;
        });
    }

    isAttacking(card) {
        return this.attackers.includes(card);
    }

    isDefending(card) {
        return this.defenders.includes(card);
    }

    isParticipating(card) {
        return this.isAttacking(card) || this.isDefending(card);
    }

    anyParticipants(predicate) {
        let participants = this.attackers.concat(this.defenders);
        return _.any(participants, predicate);
    }

    getNumberOfParticipants(predicate) {
        let participants = this.attackers.concat(this.defenders);
        return _.reduce(participants, (count, card) => {
            if(predicate(card)) {
                return count + 1;
            }

            return count;
        }, 0);
    }

    calculateSkill() {
        if(this.winnerDetermined) {
            return;
        }

        this.game.reapplyStateDependentEffects();

        this.attackerSkill = this.calculateSkillFor(this.attackers) + this.attackerSkillModifier;
        this.defenderSkill = this.calculateSkillFor(this.defenders) + this.defenderSkillModifier;

        if(this.attackingPlayer.imperialFavor === this.conflictType && this.attackers.length > 0) {
            this.attackerSkill++;
        } else if(this.defendingPlayer.imperialFavor === this.conflictType && this.defenders.length > 0) {
            this.defenderSkill++;
        }
    }

    calculateSkillFor(cards) {
        return _.reduce(cards, (sum, card) => {
            if(card.bowed || !card.allowGameAction('countForResolution')) {
                return sum;
            }
            return sum + card.getSkill(this.conflictType);
        }, 0);
    }

    modifyAttackerSkill(value) {
        this.attackerSkillModifier += value;
        this.calculateSkill();
    }

    modifyDefenderSkill(value) {
        this.defenderSkillModifier += value;
        this.calculateSkill();
    }

    determineWinner() {
        this.winnerDetermined = true;

        this.calculateSkill();

        let result = this.checkNoWinnerOrLoser();
        if(result.noWinner) {
            this.noWinnerMessage = result.message;
            this.loser = undefined;
            this.winner = undefined;
            this.loserSkill = this.winnerSkill = 0;
            this.skillDifference = 0;

            return;
        }

        if(this.attackerSkill >= this.defenderSkill) {
            this.loser = this.defendingPlayer;
            this.loserSkill = this.defenderSkill;
            this.winner = this.attackingPlayer;
            this.winnerSkill = this.attackerSkill;
        } else {
            this.loser = this.attackingPlayer;
            this.loserSkill = this.attackerSkill;
            this.winner = this.defendingPlayer;
            this.winnerSkill = this.defenderSkill;
        }

        this.winner.winConflict(this.conflictType, this.attackingPlayer === this.winner);
        this.loser.loseConflict(this.conflictType, this.attackingPlayer === this.loser);
        this.skillDifference = this.winnerSkill - this.loserSkill;
    }

    checkNoWinnerOrLoser() {
        const noWinnerRules = [
            {
                condition: () => this.attackerSkill === 0 && this.defenderSkill === 0,
                message: 'There is no winner or loser for this conflict because the attacker skill is 0'
            },
            {
                condition: () => this.attackerSkill >= this.defenderSkill && this.attackingPlayer.cannotWinConflict,
                message: 'There is no winner or loser for this conflict because the attacker cannot win'
            },
            {
                condition: () => this.attackerSkill >= this.defenderSkill && this.attackers.length === 0,
                message: 'There is no winner or loser for this conflict because the attacker has no participants'
            },
            {
                condition: () => this.defenderSkill > this.attackerSkill && this.defendingPlayer.cannotWinConflict,
                message: 'There is no winner or loser for this conflict because the defender cannot win'
            },
            {
                condition: () => this.defenderSkill > this.attackerSkill && this.defenders.length === 0,
                message: 'There is no winner or loser for this conflict because the defender has no participants'
            }
        ];

        return _.chain(noWinnerRules)
            .map(rule => ({ noWinner: !!rule.condition(), message: rule.message }))
            .find(match => match.noWinner)
            .value() || { noWinner: false };
    }

    isAttackerTheWinner() {
        return this.winner === this.attackingPlayer;
    }

    isUnopposed() {
        return this.loserSkill <= 0 && this.winnerSkill > 0;
    }

    getWinnerCards() {
        if(this.winner === this.attackingPlayer) {
            return this.attackers;
        } else if(this.winner === this.defendingPlayer) {
            return this.defenders;
        }

        return [];
    }

    getOpponentCards(player) {
        return this.attackingPlayer === player ? this.defenders : this.attackers;
    }

    finish() {
        _.each(this.attackers, card => card.inConflict = false);
        _.each(this.defenders, card => card.inConflict = false);
    }

    cancelConflict() {
        this.cancelled = true;

        this.resetCards();

        this.game.addMessage('{0} has chosen to pass their conflict opportunity', this.attackingPlayer);
    }
}

module.exports = Conflict;
<commit_msg>Remove this from develop<commit_after>const _ = require('underscore');
const Player = require('./player.js');
const Settings = require('../settings.js');

class Conflict {
    constructor(game, attackingPlayer, defendingPlayer, conflictType = '', conflictRing = '', conflictProvince = null) {
        this.game = game;
        this.attackingPlayer = attackingPlayer;
        this.isSinglePlayer = !defendingPlayer;
        this.defendingPlayer = defendingPlayer || this.singlePlayerDefender();
        this.conflictDeclaredRing = conflictRing;
        this.conflictPassed = false;
        this.conflictType = conflictType;
        this.conflictRing = conflictRing;
        this.conflictProvince = conflictProvince;
        this.conflictTypeSwitched = false;
        this.conflictUnopposed = false;
        this.winnerGoesStraightToNextConflict = false;
        this.elementsToResolve = 1;
        this.elements = [];
        this.attackers = [];
        this.attackerSkill = 0;
        this.attackerSkillModifier = 0;
        this.defenders = [];
        this.defenderSkill = 0;
        this.maxAllowedDefenders = 0;
        this.defenderSkillModifier = 0;
    }

    singlePlayerDefender() {
        let dummyPlayer = new Player('', Settings.getUserWithDefaultsSet({ name: 'Dummy Player' }), false, this.game);
        dummyPlayer.initialise();
        return dummyPlayer;
    }

    resetCards() {
        this.attackingPlayer.resetForConflict();
        this.defendingPlayer.resetForConflict();
        if(this.conflictProvince) {
            this.conflictProvince.inConflict = false;
        }
    }

    initiateConflict() {
        this.attackingPlayer.initiateConflict(this.conflictType);
    }

    addAttackers(attackers) {
        attackers = _.reject(attackers, card => this.isAttacking(card));
        if(attackers.length > 0) {
            this.attackers = this.attackers.concat(attackers);
            this.markAsParticipating(attackers);
            this.calculateSkill();
        }
    }

    addAttacker(attacker) {
        if(this.attackers.includes(attacker)) {
            return;
        }
        this.attackers.push(attacker);
        this.markAsParticipating([attacker]);
        this.calculateSkill();
    }

    addDefenders(defenders) {
        defenders = _.reject(defenders, card => this.isDefending(card));
        if(defenders.length > 0) {
            this.defenders = this.defenders.concat(defenders);
            this.markAsParticipating(defenders);
            this.calculateSkill();
        }
    }

    addDefender(defender) {
        if(this.defenders.includes(defender)) {
            return;
        }
        this.defenders.push(defender);
        this.markAsParticipating([defender]);
        this.calculateSkill();
    }

    moveToConflict(cards) {
        if(!_.isArray(cards)) {
            cards = [cards];
        }
        let events = _.map(cards, card => {
            return {
                name: 'onMoveToConflict',
                params: { conflict: this, card: card },
                handler: card.controller.isAttackingPlayer() ? () => this.addAttacker(card) : () => this.addDefender(card)
            };
        });
        this.game.raiseMultipleEvents(events, {
            name: 'onMoveCharactersToConflict',
            params: { conflict: this, cards: cards }
        });
    }

    sendHome(cards) {
        if(!_.isArray(cards)) {
            cards = [cards];
        }
        let events = _.map(cards, card => {
            return {
                name: 'onSendHome',
                params: { conflict: this, card: card },
                handler: () => this.removeFromConflict(card)
            };
        });
        this.game.raiseMultipleEvents(events, {
            name: 'onSendCharactersHome',
            params: { conflict: this, cards: cards }
        });
    }

    modifyElementsToResolve(amount) {
        this.elementsToResolve += amount;
    }

    hasElement(element) {
        return this.elements.includes(element);
    }

    getElements() {
        return _.uniq(this.elements);
    }

    addElement(element) {
        this.elements.push(element);
    }

    removeElement(element) {
        let index = _.indexOf(this.elements, element);
        if(index > -1) {
            this.elements.splice(index, 1);
        }
    }

    chooseWhetherToResolveRingEffect(player = this.attackingPlayer) {
        this.game.raiseEvent('onResolveRingEffect', { player: player, conflict: this }, () => {
            let elements = this.getElements();
            if(elements.length === 1) {
                this.game.promptWithHandlerMenu(player, {
                    activePromptTitle: 'Do you want to resolve the ' + elements[0] + ' ring?',
                    waitingPromptTitle: 'Waiting for opponent to use decide whether to resolve the conflict ring',
                    source: 'Resolve Ring Effects',
                    choices: ['Yes', 'No'],
                    handlers: [() => this.resolveConflictRing(player), () => this.game.addMessage('{0} chooses not to resolve the {1} ring', player, elements[0])]
                });
            } else {
                this.resolveConflictRing(player);
            }
            return { resolved: true, success: true };
        });
    }

    resolveConflictRing(player = this.attackingPlayer, optional = true) {
        let elements = this.getElements();
        if(elements.length === 0) {
            return;
        }
        if(elements.length === 1) {
            player.resolveRingEffects(elements, optional);
            return;
        }
        if(this.elementsToResolve >= elements.length) {
            if(optional) {
                this.game.promptWithHandlerMenu(player, {
                    activePromptTitle: 'Do you want to resolve all the elements of the conflict ring?',
                    waitingPromptTitle: 'Waiting for opponent to use decide whether to resolve the conflict ring',
                    source: 'Resolve Ring Effects',
                    choices: ['Resolve All Elements', 'Choose Elements to Resolve'],
                    handlers: [() => player.resolveRingEffects(elements, optional), () => this.chooseElementsToResolve(player, optional, this.elementsToResolve, elements)]
                });
                return;
            }
            player.resolveRingEffects(elements, optional);
            return;
        }
        this.chooseElementsToResolve(player, optional, this.elementsToResolve, elements);
    }

    chooseElementsToResolve(player, optional, elementsToResolve, elements, chosenElements = []) {
        if(elements.length === 0 || elementsToResolve === 0) {
            player.resolveRingEffects(chosenElements, optional);
            return;
        }
        let activePromptTitle = 'Choose a ring effect to resolve';
        if(chosenElements.length > 0) {
            activePromptTitle = _.reduce(chosenElements, (string, element) => string + ' ' + element, activePromptTitle + '\nChosen elements:');
        }
        this.game.promptForRingSelect(player, {
            activePromptTitle: activePromptTitle,
            ringCondition: ring => elements.includes(ring.element),
            onSelect: (player, ring) => {
                elementsToResolve--;
                chosenElements.push(ring.element);
                this.chooseElementsToResolve(player, optional, elementsToResolve, _.without(elements, ring.element), chosenElements);
                return true;
            },
            onCancel: () => player.resolveRingEffects(chosenElements, optional)
        });
    }

    switchType() {
        let ring = this.game.rings[this.conflictRing];
        ring.flipConflictType();
        this.conflictType = ring.conflictType;
        this.conflictTypeSwitched = true;
        this.game.reapplyStateDependentEffects();
        this.checkForIllegalParticipants();
    }

    switchElement(element) {
        let oldRing = this.game.rings[this.conflictRing];
        oldRing.contested = false;
        this.elements = _.reject(this.elements, element => element === oldRing.element);
        this.conflictRing = element;
        let newRing = this.game.rings[element];
        this.game.addFate(this.attackingPlayer, newRing.fate);
        newRing.fate = 0;
        newRing.contested = true;
        if(newRing.conflictType !== this.conflictType) {
            newRing.flipConflictType();
        }
        this.elements.push(element);
    }

    checkForIllegalParticipants() {
        _.each(this.attackers, card => {
            if(!card.canParticipateAsAttacker(this.conflictType)) {
                this.removeFromConflict(card);
                card.bowed = true;
            }
        });
        _.each(this.defenders, card => {
            if(!card.canParticipateAsDefender(this.conflictType)) {
                this.removeFromConflict(card);
                card.bowed = true;
            }
        });
    }

    removeFromConflict(card) {
        this.attackers = _.reject(this.attackers, c => c === card);
        this.defenders = _.reject(this.defenders, c => c === card);

        card.inConflict = false;

        this.calculateSkill();
    }

    markAsParticipating(cards) {
        _.each(cards, card => {
            card.inConflict = true;
        });
    }

    isAttacking(card) {
        return this.attackers.includes(card);
    }

    isDefending(card) {
        return this.defenders.includes(card);
    }

    isParticipating(card) {
        return this.isAttacking(card) || this.isDefending(card);
    }

    anyParticipants(predicate) {
        let participants = this.attackers.concat(this.defenders);
        return _.any(participants, predicate);
    }

    getNumberOfParticipants(predicate) {
        let participants = this.attackers.concat(this.defenders);
        return _.reduce(participants, (count, card) => {
            if(predicate(card)) {
                return count + 1;
            }

            return count;
        }, 0);
    }

    calculateSkill() {
        if(this.winnerDetermined) {
            return;
        }

        this.attackerSkill = this.calculateSkillFor(this.attackers) + this.attackerSkillModifier;
        this.defenderSkill = this.calculateSkillFor(this.defenders) + this.defenderSkillModifier;

        if(this.attackingPlayer.imperialFavor === this.conflictType && this.attackers.length > 0) {
            this.attackerSkill++;
        } else if(this.defendingPlayer.imperialFavor === this.conflictType && this.defenders.length > 0) {
            this.defenderSkill++;
        }
    }

    calculateSkillFor(cards) {
        return _.reduce(cards, (sum, card) => {
            if(card.bowed || !card.allowGameAction('countForResolution')) {
                return sum;
            }
            return sum + card.getSkill(this.conflictType);
        }, 0);
    }

    modifyAttackerSkill(value) {
        this.attackerSkillModifier += value;
        this.calculateSkill();
    }

    modifyDefenderSkill(value) {
        this.defenderSkillModifier += value;
        this.calculateSkill();
    }

    determineWinner() {
        this.winnerDetermined = true;

        this.calculateSkill();

        let result = this.checkNoWinnerOrLoser();
        if(result.noWinner) {
            this.noWinnerMessage = result.message;
            this.loser = undefined;
            this.winner = undefined;
            this.loserSkill = this.winnerSkill = 0;
            this.skillDifference = 0;

            return;
        }

        if(this.attackerSkill >= this.defenderSkill) {
            this.loser = this.defendingPlayer;
            this.loserSkill = this.defenderSkill;
            this.winner = this.attackingPlayer;
            this.winnerSkill = this.attackerSkill;
        } else {
            this.loser = this.attackingPlayer;
            this.loserSkill = this.attackerSkill;
            this.winner = this.defendingPlayer;
            this.winnerSkill = this.defenderSkill;
        }

        this.winner.winConflict(this.conflictType, this.attackingPlayer === this.winner);
        this.loser.loseConflict(this.conflictType, this.attackingPlayer === this.loser);
        this.skillDifference = this.winnerSkill - this.loserSkill;
    }

    checkNoWinnerOrLoser() {
        const noWinnerRules = [
            {
                condition: () => this.attackerSkill === 0 && this.defenderSkill === 0,
                message: 'There is no winner or loser for this conflict because the attacker skill is 0'
            },
            {
                condition: () => this.attackerSkill >= this.defenderSkill && this.attackingPlayer.cannotWinConflict,
                message: 'There is no winner or loser for this conflict because the attacker cannot win'
            },
            {
                condition: () => this.attackerSkill >= this.defenderSkill && this.attackers.length === 0,
                message: 'There is no winner or loser for this conflict because the attacker has no participants'
            },
            {
                condition: () => this.defenderSkill > this.attackerSkill && this.defendingPlayer.cannotWinConflict,
                message: 'There is no winner or loser for this conflict because the defender cannot win'
            },
            {
                condition: () => this.defenderSkill > this.attackerSkill && this.defenders.length === 0,
                message: 'There is no winner or loser for this conflict because the defender has no participants'
            }
        ];

        return _.chain(noWinnerRules)
            .map(rule => ({ noWinner: !!rule.condition(), message: rule.message }))
            .find(match => match.noWinner)
            .value() || { noWinner: false };
    }

    isAttackerTheWinner() {
        return this.winner === this.attackingPlayer;
    }

    isUnopposed() {
        return this.loserSkill <= 0 && this.winnerSkill > 0;
    }

    getWinnerCards() {
        if(this.winner === this.attackingPlayer) {
            return this.attackers;
        } else if(this.winner === this.defendingPlayer) {
            return this.defenders;
        }

        return [];
    }

    getOpponentCards(player) {
        return this.attackingPlayer === player ? this.defenders : this.attackers;
    }

    finish() {
        _.each(this.attackers, card => card.inConflict = false);
        _.each(this.defenders, card => card.inConflict = false);
    }

    cancelConflict() {
        this.cancelled = true;

        this.resetCards();

        this.game.addMessage('{0} has chosen to pass their conflict opportunity', this.attackingPlayer);
    }
}

module.exports = Conflict;
<|endoftext|>