<commit_before>/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# SETUP THE APP on HEROKU:
  Create a Slack app. Make sure to configure the bot user!
    -> https://api.slack.com/applications/new
    -> Add the Redirect URI: https://${APP_NAME}.herokuapp.com/oauth
  Deply and run your app on Heroku.
    -> Add Config Vars on the Heroku app setting page: clientId, clientSecret
    -> Deploy and run on Heroku.
  Set RequestURL on Slack.
    -> Add the RequesteURL of Interactive Message: https://${APP_NAME}.herokuapp.com/slack/receive
# USE THE APP
  Add the app to your Slack by visiting the login page:
    -> https://${APP_NAME}.herokuapp.com/login
  After you've added the app, try talking to your bot!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

var Botkit = require('botkit');
var MongoUrl = process.env.MONGODB_URI || 'mongodb://localhost:27017/zangyo-bot';
var moment = require('moment');

if (!process.env.clientId || !process.env.clientSecret || !process.env.port) {
  console.log('Error: Specify clientId clientSecret and port in environment');
  process.exit(1);
}

var controller = Botkit.slackbot({
  // interactive_replies: true, // tells botkit to send button clicks into conversations
  storage: require('botkit-storage-mongo')({mongoUri: MongoUrl})
}).configureSlackApp(
  {
    clientId: process.env.clientId,
    clientSecret: process.env.clientSecret,
    scopes: ['bot','incoming-webhook'],
  }
);

controller.setupWebserver(process.env.port,function(err,webserver) {
  controller.createWebhookEndpoints(controller.webserver);

  controller.createOauthEndpoints(controller.webserver,function(err,req,res) {
    if (err) {
      res.status(500).send('ERROR: ' + err);
    } else {
      res.send('Success!');
    }
  });
});

// just a simple way to make sure we don't
// connect to the RTM twice for the same team
var _bots = {};
function trackBot(bot) {
  _bots[bot.config.token] = bot;
}

controller.on('create_bot',function(bot,config) {

  if (_bots[bot.config.token]) {
    // already online! do nothing.
  } else {
    bot.startRTM(function(err) {

      if (!err) {
        trackBot(bot);
      }

      bot.startPrivateConversation({user: config.createdBy},function(err,convo) {
        if (err) {
          console.log(err);
        } else {
          convo.say('I am a bot that has just joined your team');
          convo.say('You must now /invite me to a channel so that I can be of use!');
        }
      });

    });
  }

});

// Handle events related to the websocket connection to Slack
controller.on('rtm_open',function(bot) {
  console.log('** The RTM api just connected!');
});

controller.on('rtm_close',function(bot) {
  console.log('** The RTM api just closed');
  // you may want to attempt to re-open
});

controller.hears('button', ['direct_message'],function(bot,message) {
  var reply = {
    "text": "ボタンのテストです。",
    "attachments": [{
      "text": "どれか押してください。",
      "fallback": "失敗しました。",
      "callback_id": "test_button",
      "color": "#808080",
      "actions": [
        {
          "type": "button",
          "name": "test_button1",
          "text": "テストボタン1"
        },
        {
          "type": "button",
          "name": "test_button2",
          "text": "テストボタン2"
        }
      ]
    }]
  };
  bot.reply(message, reply);
});

controller.hears('^.*残業.*申請.*',['direct_message','direct_mention'],function(bot,message) {
  bot.startConversation(message, askApprover);
});

askApprover = function(response, convo) {
  convo.ask("プロマネは誰？ [@xxx]", function(response, convo) {
    if (!response.text.match(/^<@[a-zA-Z0-9]*>$/)) {
      convo.say('@xxx の形式でユーザーを１人指定してね。');
      askApprover(response, convo);
      convo.next();
    } else {
      var zangyo = {}
      zangyo.id = uuid();
      zangyo.date = moment().format("YYYY/MM/DD");
      zangyo.applicant = response.user;
      zangyo.approver = response.text.slice(2, -1);
      askEndTime(response, convo, zangyo);
      convo.next();
    }
  });
}
askEndTime = function(response, convo, zangyo) {
  convo.ask("何時に終わる？ [HH:MM]", function(response, convo) {
    if (!response.text.match(/^([0-2]?[0-9]):([0-5]?[0-9])$/)) {
      convo.say('HH:MM の形式で時間を指定してね。29:59まで指定できるよ。');
      askEndTime(response, convo, zangyo);
      convo.next();
    } else {
      zangyo.endTime = response.text;
      askReason(response, convo, zangyo);
      convo.next();
    }
  });
}
askReason = function(response, convo, zangyo) {
  convo.ask("残業する理由は？", function(response, convo) {
    zangyo.reason = response.text;
    var summary = {
      "text": "この内容で残業申請しますか？",
      "attachments": [
        {
          "text": "申請内容まとめ(" + zangyo.date + ")",
          "fallback": "申請内容のまとめ",
          "callback_id": "apply-" + response.user + '-' + zangyo.id,
          "color": "#36a64f",
          "fields": [
            {
              "title": "申請者",
              "value": "<@" + zangyo.applicant + ">",
              "short": false
            },
            {
              "title": "承認者",
              "value": "<@" + zangyo.approver + ">",
              "short": false
            },
            {
              "title": "終了時間",
              "value": zangyo.endTime,
              "short": false
            },
            {
              "title": "残業する理由",
              "value": zangyo.reason,
              "short": false
            }
          ],
          "actions": [
            {
              "type": "button",
              "name": "apply",
              "text": "申請"
            },
            {
              "type": "button",
              "name": "redo",
              "text": "やり直し"
            },
            {
              "type": "button",
              "name": "cancel",
              "text": "キャンセル"
            }
          ]
        }
      ]
    }
    controller.storage.users.get(response.user, function(err, user) {
        if (!user) {
          user = {
            id: response.user,
            zangyos: []
          }
        } else if (!user.zangyos) {
          user.zangyos = [];
        }

        user.zangyos.push(zangyo);
        controller.storage.users.save(user);
    });
    convo.say(summary);
    convo.next();
  });
}

controller.on('interactive_message_callback', function(bot, message) {
  var ids = message.callback_id.split(/\-/);
  var action = ids[0];
  var user_id = ids[1];
  var item_id = ids[2];

  if (action == 'apply') {
    var ans = message.actions[0].name;
    controller.storage.users.get(user_id, function(err, user) {
      for (var x = 0; x < user.zangyos.length; x++) {
        if (user.zangyos[x].id == item_id) {
          if (ans == 'apply') {
            var zangyo = user.zangyos[x];
            var summary = {
              "attachments": [
                {
                  "text": "申請内容まとめ(" + zangyo.date + ")",
                  "fallback": "申請内容のまとめ",
                  "color": "#36a64f",
                  "fields": [
                    {
                      "title": "申請者",
                      "value": "<@" + zangyo.applicant + ">",
                      "short": false
                    },
                    {
                      "title": "承認者",
                      "value": "<@" + zangyo.approver + ">",
                      "short": false
                    },
                    {
                      "title": "終了時間",
                      "value": zangyo.endTime,
                      "short": false
                    },
                    {
                      "title": "残業する理由",
                      "value": zangyo.reason,
                      "short": false
                    }
                  ]
                }
              ]
            }
            bot.replyInteractive(message, summary);
            bot.reply(message, "この内容で残業申請したよ。");
            bot.startPrivateConversation({user: zangyo.approver}, function(err,convo) {
              if (err) {
                console.log(err);
              } else {
                var application = {
                  "text": "残業申請があります。承認しますか？",
                  "attachments": [
                    {
                      "text": "申請内容まとめ(" + zangyo.date + ")",
                      "fallback": "申請内容のまとめ",
                      "callback_id": "approve-" + zangyo.applicant + '-' + zangyo.id,
                      "color": "#36a64f",
                      "fields": [
                        {
                          "title": "申請者",
                          "value": "<@" + zangyo.applicant + ">",
                          "short": false
                        },
                        {
                          "title": "承認者",
                          "value": "<@" + zangyo.approver + ">",
                          "short": false
                        },
                        {
                          "title": "終了時間",
                          "value": zangyo.endTime,
                          "short": false
                        },
                        {
                          "title": "残業する理由",
                          "value": zangyo.reason,
                          "short": false
                        }
                      ],
                      "actions": [
                        {
                          "type": "button",
                          "name": "approve",
                          "text": "承認"
                        },
                        {
                          "type": "button",
                          "name": "reject",
                          "text": "却下"
                        }
                      ]
                    }
                  ]
                }
                convo.say(application);
                convo.next();
              }
            });
          } else if (ans == 'redo') {
            user.zangyos.splice(x, 1);
            bot.replyInteractive(message, "最初からやり直し！");
            bot.startConversation(message, askApprover);
          } else {
            user.zangyos.splice(x, 1);
            bot.replyInteractive(message, "キャンセルしたよ。さっさと帰ろう！");
          }
          controller.storage.users.save(user);
          break;
        }
      }
    });
  } else if (action == 'approve') {
    var ans = message.actions[0].name;
    controller.storage.users.get(user_id, function(err, user) {
      for (var x = 0; x < user.zangyos.length; x++) {
        if (user.zangyos[x].id == item_id) {
          var zangyo = user.zangyos[x];
          var summary = {
            "attachments": [
              {
                "text": "申請内容まとめ(" + zangyo.date + ")",
                "fallback": "申請内容のまとめ",
                "color": "#36a64f",
                "fields": [
                  {
                    "title": "申請者",
                    "value": "<@" + zangyo.applicant + ">",
                    "short": false
                  },
                  {
                    "title": "承認者",
                    "value": "<@" + zangyo.approver + ">",
                    "short": false
                  },
                  {
                    "title": "終了時間",
                    "value": zangyo.endTime,
                    "short": false
                  },
                  {
                    "title": "残業する理由",
                    "value": zangyo.reason,
                    "short": false
                  }
                ]
              }
            ]
          }
          bot.replyInteractive(message, summary);
          if (ans == 'approve') {
            user.zangyos[x].approved = true;
            bot.reply(message, "この申請を承認したよ。");

            controller.storage.users.get(zangyo.applicant, function(err, user) {
              controller.storage.teams.get(user.team_id, function(err, team) {
                summary.text = "この申請が承認されました。";
                bot.configureIncomingWebhook(team.incoming_webhook);
                bot.sendWebhook({
                  text: summary
                },function(err,res) {
                  if (err) console.log(err);
                });
              });
            });

            // bot.startPrivateConversation({user: zangyo.applicant}, function(err,convo) {
            //   if (err) {
            //     console.log(err);
            //   } else {
            //     convo.say(summary);
            //     convo.say("この申請が承認されました。");
            //     convo.next();
            //   }
            // });
          } else if (ans == 'reject') {
            user.zangyos[x].approved = false;
            bot.reply(message, "この申請を却下したよ。");

            controller.storage.users.get(zangyo.applicant, function(err, user) {
              controller.storage.teams.get(user.team_id, function(err, team) {
                summary.text = "この申請が却下されました。";
                bot.configureIncomingWebhook(team.incoming_webhook);
                bot.sendWebhook({
                  text: summary
                },function(err,res) {
                  if (err) console.log(err);
                });
              });
            });

            // bot.startPrivateConversation({user: zangyo.applicant}, function(err,convo) {
            //   if (err) {
            //     console.log(err);
            //   } else {
            //     convo.say(summary);
            //     convo.say("この申請が却下されました。");
            //     convo.next();
            //   }
            // });
          }
          controller.storage.users.save(user);
          break;
        }
      }
    });
  } else if (message.callback_id == "test_button") {
    var users_answer = message.actions[0].name;
    bot.replyInteractive(message, "あなたは「" + users_answer + "」を押しました");
  }
});

controller.storage.teams.all(function(err,teams) {

  if (err) {
    throw new Error(err);
  }

  // connect all teams with bots up to slack!
  for (var t  in teams) {
    if (teams[t].bot) {
      controller.spawn(teams[t]).startRTM(function(err, bot) {
        if (err) {
          console.log('Error connecting bot to Slack:',err);
        } else {
          trackBot(bot);
        }
      });
    }
  }
});

function uuid() {
  var uuid = "", i, random;
  for (i = 0; i < 32; i++) {
    random = Math.random() * 16 | 0;
    uuid += (i == 12 ? 4 : (i == 16 ? (random & 3 | 8) : random)).toString(16);
  }
  return uuid;
};
<commit_msg>hook! do it! webhook!<commit_after>/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# SETUP THE APP on HEROKU:
  Create a Slack app. Make sure to configure the bot user!
    -> https://api.slack.com/applications/new
    -> Add the Redirect URI: https://${APP_NAME}.herokuapp.com/oauth
  Deply and run your app on Heroku.
    -> Add Config Vars on the Heroku app setting page: clientId, clientSecret
    -> Deploy and run on Heroku.
  Set RequestURL on Slack.
    -> Add the RequesteURL of Interactive Message: https://${APP_NAME}.herokuapp.com/slack/receive
# USE THE APP
  Add the app to your Slack by visiting the login page:
    -> https://${APP_NAME}.herokuapp.com/login
  After you've added the app, try talking to your bot!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

var Botkit = require('botkit');
var MongoUrl = process.env.MONGODB_URI || 'mongodb://localhost:27017/zangyo-bot';
var moment = require('moment');

if (!process.env.clientId || !process.env.clientSecret || !process.env.port) {
  console.log('Error: Specify clientId clientSecret and port in environment');
  process.exit(1);
}

var controller = Botkit.slackbot({
  // interactive_replies: true, // tells botkit to send button clicks into conversations
  storage: require('botkit-storage-mongo')({mongoUri: MongoUrl})
}).configureSlackApp(
  {
    clientId: process.env.clientId,
    clientSecret: process.env.clientSecret,
    scopes: ['bot','incoming-webhook'],
  }
);

controller.setupWebserver(process.env.port,function(err,webserver) {
  controller.createWebhookEndpoints(controller.webserver);

  controller.createOauthEndpoints(controller.webserver,function(err,req,res) {
    if (err) {
      res.status(500).send('ERROR: ' + err);
    } else {
      res.send('Success!');
    }
  });
});

// just a simple way to make sure we don't
// connect to the RTM twice for the same team
var _bots = {};
function trackBot(bot) {
  _bots[bot.config.token] = bot;
}

controller.on('create_bot',function(bot,config) {

  if (_bots[bot.config.token]) {
    // already online! do nothing.
  } else {
    bot.startRTM(function(err) {

      if (!err) {
        trackBot(bot);
      }

      bot.startPrivateConversation({user: config.createdBy},function(err,convo) {
        if (err) {
          console.log(err);
        } else {
          convo.say('I am a bot that has just joined your team');
          convo.say('You must now /invite me to a channel so that I can be of use!');
        }
      });

    });
  }

});

// Handle events related to the websocket connection to Slack
controller.on('rtm_open',function(bot) {
  console.log('** The RTM api just connected!');
});

controller.on('rtm_close',function(bot) {
  console.log('** The RTM api just closed');
  // you may want to attempt to re-open
});

controller.hears('button', ['direct_message'],function(bot,message) {
  var reply = {
    "text": "ボタンのテストです。",
    "attachments": [{
      "text": "どれか押してください。",
      "fallback": "失敗しました。",
      "callback_id": "test_button",
      "color": "#808080",
      "actions": [
        {
          "type": "button",
          "name": "test_button1",
          "text": "テストボタン1"
        },
        {
          "type": "button",
          "name": "test_button2",
          "text": "テストボタン2"
        }
      ]
    }]
  };
  bot.reply(message, reply);
});

controller.hears('^.*残業.*申請.*',['direct_message','direct_mention'],function(bot,message) {
  bot.startConversation(message, askApprover);
});

askApprover = function(response, convo) {
  convo.ask("プロマネは誰？ [@xxx]", function(response, convo) {
    if (!response.text.match(/^<@[a-zA-Z0-9]*>$/)) {
      convo.say('@xxx の形式でユーザーを１人指定してね。');
      askApprover(response, convo);
      convo.next();
    } else {
      var zangyo = {}
      zangyo.id = uuid();
      zangyo.date = moment().format("YYYY/MM/DD");
      zangyo.applicant = response.user;
      zangyo.approver = response.text.slice(2, -1);
      askEndTime(response, convo, zangyo);
      convo.next();
    }
  });
}
askEndTime = function(response, convo, zangyo) {
  convo.ask("何時に終わる？ [HH:MM]", function(response, convo) {
    if (!response.text.match(/^([0-2]?[0-9]):([0-5]?[0-9])$/)) {
      convo.say('HH:MM の形式で時間を指定してね。29:59まで指定できるよ。');
      askEndTime(response, convo, zangyo);
      convo.next();
    } else {
      zangyo.endTime = response.text;
      askReason(response, convo, zangyo);
      convo.next();
    }
  });
}
askReason = function(response, convo, zangyo) {
  convo.ask("残業する理由は？", function(response, convo) {
    zangyo.reason = response.text;
    var summary = {
      "text": "この内容で残業申請しますか？",
      "attachments": [
        {
          "text": "申請内容まとめ(" + zangyo.date + ")",
          "fallback": "申請内容のまとめ",
          "callback_id": "apply-" + response.user + '-' + zangyo.id,
          "color": "#36a64f",
          "fields": [
            {
              "title": "申請者",
              "value": "<@" + zangyo.applicant + ">",
              "short": false
            },
            {
              "title": "承認者",
              "value": "<@" + zangyo.approver + ">",
              "short": false
            },
            {
              "title": "終了時間",
              "value": zangyo.endTime,
              "short": false
            },
            {
              "title": "残業する理由",
              "value": zangyo.reason,
              "short": false
            }
          ],
          "actions": [
            {
              "type": "button",
              "name": "apply",
              "text": "申請"
            },
            {
              "type": "button",
              "name": "redo",
              "text": "やり直し"
            },
            {
              "type": "button",
              "name": "cancel",
              "text": "キャンセル"
            }
          ]
        }
      ]
    }
    controller.storage.users.get(response.user, function(err, user) {
        if (!user) {
          user = {
            id: response.user,
            zangyos: []
          }
        } else if (!user.zangyos) {
          user.zangyos = [];
        }

        user.zangyos.push(zangyo);
        controller.storage.users.save(user);
    });
    convo.say(summary);
    convo.next();
  });
}

controller.on('interactive_message_callback', function(bot, message) {
  var ids = message.callback_id.split(/\-/);
  var action = ids[0];
  var user_id = ids[1];
  var item_id = ids[2];

  if (action == 'apply') {
    var ans = message.actions[0].name;
    controller.storage.users.get(user_id, function(err, user) {
      for (var x = 0; x < user.zangyos.length; x++) {
        if (user.zangyos[x].id == item_id) {
          if (ans == 'apply') {
            var zangyo = user.zangyos[x];
            var summary = {
              "attachments": [
                {
                  "text": "申請内容まとめ(" + zangyo.date + ")",
                  "fallback": "申請内容のまとめ",
                  "color": "#36a64f",
                  "fields": [
                    {
                      "title": "申請者",
                      "value": "<@" + zangyo.applicant + ">",
                      "short": false
                    },
                    {
                      "title": "承認者",
                      "value": "<@" + zangyo.approver + ">",
                      "short": false
                    },
                    {
                      "title": "終了時間",
                      "value": zangyo.endTime,
                      "short": false
                    },
                    {
                      "title": "残業する理由",
                      "value": zangyo.reason,
                      "short": false
                    }
                  ]
                }
              ]
            }
            bot.replyInteractive(message, summary);
            bot.reply(message, "この内容で残業申請したよ。");
            bot.startPrivateConversation({user: zangyo.approver}, function(err,convo) {
              if (err) {
                console.log(err);
              } else {
                var application = {
                  "text": "残業申請があります。承認しますか？",
                  "attachments": [
                    {
                      "text": "申請内容まとめ(" + zangyo.date + ")",
                      "fallback": "申請内容のまとめ",
                      "callback_id": "approve-" + zangyo.applicant + '-' + zangyo.id,
                      "color": "#36a64f",
                      "fields": [
                        {
                          "title": "申請者",
                          "value": "<@" + zangyo.applicant + ">",
                          "short": false
                        },
                        {
                          "title": "承認者",
                          "value": "<@" + zangyo.approver + ">",
                          "short": false
                        },
                        {
                          "title": "終了時間",
                          "value": zangyo.endTime,
                          "short": false
                        },
                        {
                          "title": "残業する理由",
                          "value": zangyo.reason,
                          "short": false
                        }
                      ],
                      "actions": [
                        {
                          "type": "button",
                          "name": "approve",
                          "text": "承認"
                        },
                        {
                          "type": "button",
                          "name": "reject",
                          "text": "却下"
                        }
                      ]
                    }
                  ]
                }
                convo.say(application);
                convo.next();
              }
            });
          } else if (ans == 'redo') {
            user.zangyos.splice(x, 1);
            bot.replyInteractive(message, "最初からやり直し！");
            bot.startConversation(message, askApprover);
          } else {
            user.zangyos.splice(x, 1);
            bot.replyInteractive(message, "キャンセルしたよ。さっさと帰ろう！");
          }
          controller.storage.users.save(user);
          break;
        }
      }
    });
  } else if (action == 'approve') {
    var ans = message.actions[0].name;
    controller.storage.users.get(user_id, function(err, user) {
      for (var x = 0; x < user.zangyos.length; x++) {
        if (user.zangyos[x].id == item_id) {
          var zangyo = user.zangyos[x];
          var summary = {
            "attachments": [
              {
                "text": "申請内容まとめ(" + zangyo.date + ")",
                "fallback": "申請内容のまとめ",
                "color": "#36a64f",
                "fields": [
                  {
                    "title": "申請者",
                    "value": "<@" + zangyo.applicant + ">",
                    "short": false
                  },
                  {
                    "title": "承認者",
                    "value": "<@" + zangyo.approver + ">",
                    "short": false
                  },
                  {
                    "title": "終了時間",
                    "value": zangyo.endTime,
                    "short": false
                  },
                  {
                    "title": "残業する理由",
                    "value": zangyo.reason,
                    "short": false
                  }
                ]
              }
            ]
          }
          bot.replyInteractive(message, summary);
          if (ans == 'approve') {
            user.zangyos[x].approved = true;
            bot.reply(message, "この申請を承認したよ。");

            controller.storage.users.get(zangyo.applicant, function(err, user) {
              controller.storage.teams.get(user.team_id, function(err, team) {
                summary.text = "この申請が承認されました。";
                bot.configureIncomingWebhook(team.incoming_webhook);
                bot.sendWebhook(summary, function(err,res) {
                  if (err) console.log(err);
                });
              });
            });

            // bot.startPrivateConversation({user: zangyo.applicant}, function(err,convo) {
            //   if (err) {
            //     console.log(err);
            //   } else {
            //     convo.say(summary);
            //     convo.say("この申請が承認されました。");
            //     convo.next();
            //   }
            // });
          } else if (ans == 'reject') {
            user.zangyos[x].approved = false;
            bot.reply(message, "この申請を却下したよ。");

            controller.storage.users.get(zangyo.applicant, function(err, user) {
              controller.storage.teams.get(user.team_id, function(err, team) {
                summary.text = "この申請が却下されました。";
                bot.configureIncomingWebhook(team.incoming_webhook);
                bot.sendWebhook(summary, function(err,res) {
                  if (err) console.log(err);
                });
              });
            });

            // bot.startPrivateConversation({user: zangyo.applicant}, function(err,convo) {
            //   if (err) {
            //     console.log(err);
            //   } else {
            //     convo.say(summary);
            //     convo.say("この申請が却下されました。");
            //     convo.next();
            //   }
            // });
          }
          controller.storage.users.save(user);
          break;
        }
      }
    });
  } else if (message.callback_id == "test_button") {
    var users_answer = message.actions[0].name;
    bot.replyInteractive(message, "あなたは「" + users_answer + "」を押しました");
  }
});

controller.storage.teams.all(function(err,teams) {

  if (err) {
    throw new Error(err);
  }

  // connect all teams with bots up to slack!
  for (var t  in teams) {
    if (teams[t].bot) {
      controller.spawn(teams[t]).startRTM(function(err, bot) {
        if (err) {
          console.log('Error connecting bot to Slack:',err);
        } else {
          trackBot(bot);
        }
      });
    }
  }
});

function uuid() {
  var uuid = "", i, random;
  for (i = 0; i < 32; i++) {
    random = Math.random() * 16 | 0;
    uuid += (i == 12 ? 4 : (i == 16 ? (random & 3 | 8) : random)).toString(16);
  }
  return uuid;
};
<|endoftext|>


<commit_before>var semver = require('semver')

function isFunction (f) {
  return 'function' === typeof f
}

function isObject (o) {
  return o && 'object' === typeof o
}

function find (ary, test) {
  for(var i in ary)
    if(test(ary[i], i, ary))
      return ary[i]
}

function each(obj, iter) {
  for(var k in obj)
    iter(obj[k], k, obj)
}

function copy (_a, a) {
  _a = _a || {}
  for(var k in a)
    _a[k] = _a[k] || a[k]
  return _a
}

function merge(ary) {
  return ary.reduce(copy, {})
}

function _pluck(name) {
  return function (obj) {
    return obj[name]
  }
}

function allow () {

  function perms () {}

  perms.pre = function () {}
  perms.post = function (value) {
    return value
  }

  return perms
}

module.exports = function () {

  var layers = []

  function resolve(name, range) {
    return find(layers, function (layer) {
      return name === layer.name && semver.satisfies(layer.version, range)
    })
  }

  return {
    api: function (perms) {
      //return a flatterned api snapshot.
      var root = {}

      perms = perms || allow()

      //add layers into one api object under their root[name]
      //except where layer.core === true, add those directly to root
      layers.forEach(function (layer) {
        var api, path = []
        if(layer.core) api = root
        else {
          api = root[layer.name] = {}
          path = [layer.name]
        }
        each(layer.manifest, function (type, name) {
          var _path = path.concat(name)
          api[name] = function (arg, cb) {
            perms.pre(arg, _path, type) //should throw if invalid.

            var value = layer.api[name].call(root, arg)
            value = perms.post(value, _path, type)
            if(cb && type === 'async') value(cb)
            else                       return value
          }
        })
      })

      return root
    },

    //install a subapi.
    use: function (layer) {
      if(!layer) layer = name, name = null

      //check it's actually possible to use this layer.
      if(isObject(layer.dependencies))
        each(layer.dependencies, function (range, name) {
          if(!resolve(name, range))
            throw new Error('missing dependency:'+name+'@'+version)
        })

      //initialize the api... but what about the api's permissions?
      layers.push(layer)

      return this
    },

    manifest: function () {
      console.log(layers.map(_pluck('manifest')))
      return merge(layers.map(function (layer) {
        if(layer.core) return layer.manifest
        var o = {}
        o[layer.name] = layer.manifest
        return o
      }))
    }
  }

}

<commit_msg>init functions like in scuttlebot<commit_after>var semver = require('semver')

function isFunction (f) {
  return 'function' === typeof f
}

function isObject (o) {
  return o && 'object' === typeof o
}

function find (ary, test) {
  for(var i in ary)
    if(test(ary[i], i, ary))
      return ary[i]
}

function each(obj, iter) {
  for(var k in obj)
    iter(obj[k], k, obj)
}

function copy (_a, a) {
  _a = _a || {}
  for(var k in a)
    _a[k] = _a[k] || a[k]
  return _a
}

function merge(ary) {
  return ary.reduce(copy, {})
}

function _pluck(name) {
  return function (obj) {
    return obj[name]
  }
}

function allow () {

  function perms () {}

  perms.pre = function () {}
  perms.post = function (value) {
    return value
  }

  return perms
}

module.exports = function () {

  var layers = []

  function resolve(name, range) {
    return find(layers, function (layer) {
      return name === layer.name && semver.satisfies(layer.version, range)
    })
  }

  return {
    api: function (perms) {
      //return a flatterned api snapshot.
      var root = {}

      perms = perms || allow()

      //add layers into one api object under their root[name]
      //except where layer.core === true, add those directly to root
      layers.forEach(function (layer) {
        var api, path = []
        if(layer.core) api = root
        else {
          api = root[layer.name] = {}
          path = [layer.name]
        }
        each(layer.manifest, function (type, name) {
          var _path = path.concat(name)
          api[name] = function (arg, cb) {
            perms.pre(arg, _path, type) //should throw if invalid.

            var value = layer.api[name].call(root, arg)
            value = perms.post(value, _path, type)
            if(cb && type === 'async') value(cb)
            else                       return value
          }
        })
      })

      return root
    },

    //install a subapi.
    use: function (layer) {
      if(!layer) layer = name, name = null

      //check it's actually possible to use this layer.
      if(isObject(layer.dependencies))
        each(layer.dependencies, function (range, name) {
          if(!resolve(name, range))
            throw new Error('missing dependency:'+name+'@'+version)
        })

      //initialize the api... but what about the api's permissions?

      if(layer.init) {
        var API = this.api()
        layer.api = layer.init.call(API, API)
      }

      layers.push(layer)

      return this
    },

    manifest: function () {
      console.log(layers.map(_pluck('manifest')))
      return merge(layers.map(function (layer) {
        if(layer.core) return layer.manifest
        var o = {}
        o[layer.name] = layer.manifest
        return o
      }))
    }
  }

}

<|endoftext|>


<commit_before>var varint = require('varint')

var ONE = 128, TWO = 128*128, THREE = 128*128*128, FOUR = 128*128*128*128

//I'll make this into a pull request for varint later
//if this turns out to be a good idea.

varint.dynamicLength = function (v) {
  return (
    v <= ONE   ? 1
  : v <= TWO   ? 2
  : v <= THREE ? 3
  : v <= FOUR  ? 4
  :              5
  )
}

function reduce(obj, iter, acc) {
  for(var k in obj)
    acc = iter(acc, obj[k], k, obj)
  return acc
}

exports = module.exports = function (parts) {
  var funLen = false
  var length = reduce(parts, function (acc, v) {
    if(isNaN(v.length)) return funLen = true
    return acc + v.length
  }, 0)

  function lengthOf(part, value) {
    return part.length || part.dynamicLength(value)
  }

  function getLength(obj) {
    return reduce(parts, function (acc, part, k) {
      return acc + lengthOf(part, obj[k])
    }, 0)
  }

  return {
    encode: function (obj) {
      var b = new Buffer(funLen ? getLength(obj) : length )
      var offset = 0
      for(var k in parts) {
        parts[k].encode(obj[k], b, offset)
        offset += lengthOf(parts[k], obj[k])
      }
      return b
    },
    decode: function (buffer) {
      var obj = {}
      var offset = 0
      for(var k in parts) {
        obj[k] = parts[k].decode(buffer, offset)
        offset += lengthOf(parts[k], obj[k]) | 0
      }
      return obj
    },
    length: funLen ? null : length,
    dynamicLength: getLength
  }
}

exports.int8 = {
  encode: function (value, buffer, offset) {
    if(!buffer) return new Buffer([value & 0xff])
    console.log('byte-set', value, buffer, offset)
    buffer[offset] = value
    return buffer
  },
  decode: function (buffer, offset) {
    return buffer[offset | 0]
  },
  length: 1
}

function createNumber(type, len) {
  var read = Buffer.prototype['read' + type]
  var write = Buffer.prototype['write' + type]
  return {
    encode: function (value, b, offset) {
      b = b || new Buffer(len)
      write.call(b, value, offset | 0)
      return b
    },
    decode: function (buffer, offset) {
      return read.call(buffer, offset|0)
    },
    length: len
  }
}

exports.byte =
exports.Int8 =
exports.UInt8 = createNumber('UInt8', 1)

exports.UInt16BE = createNumber('UInt16BE', 1)
exports.UInt32BE = createNumber('UInt32BE', 4)
exports.FloatBE  = createNumber('FloatBE', 4)
exports.DoubleBE = createNumber('DoubleBE', 8)

exports.UInt16BE = createNumber('UInt16LE', 1)
exports.UInt32BE = createNumber('UInt32LE', 4)
exports.FloatBE  = createNumber('FloatLE', 4)
exports.DoubleBE = createNumber('DoubleLE', 8)

exports.UInt16 = exports.UInt16BE
exports.UInt32 = exports.UInt32BE
exports.Float = exports.FloatBE
exports.Double = exports.DoubleBE

exports.buffer =
exports.array = function (len) {
  return {
    encode: function (value, b, offset) {
      //already encodes a buffer, so if there is no b just return.
      if(!b) return value
      value.copy(b, offset, 0, len)
      return b
    },
    decode: function (buffer, offset) {
      return buffer.slice(offset, offset + len)
    },
    length: len
  }
}

exports.varbuf = function (lenType) {
  return {
    encode: function (value, buffer, offset) {
      buffer = buffer || new Buffer(this.dynamicLength(value) )
      offset = offset | 0
      buffer = lenType.encode(value.length, buffer, offset)
      offset += lenType.length || lenType.dynamicLength(value.length)
      value.copy(buffer, offset, 0, value.length)
      return buffer
    },
    decode: function (buffer, offset) {
      offset = offset | 0
      var length = lenType.decode(buffer, offset)
      offset += lenType.length || lenType.dynamicLength(length)
      return buffer.slice(offset, offset + length)
    },
    dynamicLength: function (value) {
      return value.length + (lenType.length || lenType.dynamicLength(value.length))
    }
  }
}

exports.varint = varint

exports.vararray = function (lenType, itemType) {
  return {
    encode: function (value, buffer, offset) {
      if(!Array.isArray(value))
        throw new Error('can only encode arrays')
      if(!buffer) {
        buffer = new Buffer(this.dynamicLength(value))
        offset = 0
      }
      var contentLength = value.length*itemType.length
      lenType.encode(contentLength, buffer, offset)
      offset += lenType.length || lenType.dynamicLength(contentLength)
      value.forEach(function (e) {
        itemType.encode(e, buffer, offset)
        offset += itemType.length || itemType.dynamicLength(e)
      })
      return buffer
    },
    decode: function (buffer, offset) {
      offset = offset | 0
      var length = lenType.decode(buffer, offset)
      offset += lenType.length || lenType.dynamicLength(length)
      o = offset
      var array = []
      while(o < offset + length) {
        var last = itemType.decode(buffer, o)
        o += itemType.length || itemType.dynamicLength(last)
        array.push(last)
      }
      return array
    },
    dynamicLength: function (value) {
      var contentLength = value.length*itemType.length
      return (
        contentLength
      + (lenType.length || lenType.dynamicLength(contentLength))
      )
    }
  }
}
<commit_msg>implement bytesRead/bytesWritten<commit_after>var varint = require('varint')

var ONE = 128, TWO = 128*128, THREE = 128*128*128, FOUR = 128*128*128*128

//I'll make this into a pull request for varint later
//if this turns out to be a good idea.

varint.dynamicLength = function (v) {
  return (
    v <= ONE   ? 1
  : v <= TWO   ? 2
  : v <= THREE ? 3
  : v <= FOUR  ? 4
  :              5
  )
}

function reduce(obj, iter, acc) {
  for(var k in obj)
    acc = iter(acc, obj[k], k, obj)
  return acc
}

exports = module.exports = function (parts) {
  var funLen = false
  var length = reduce(parts, function (acc, v) {
    if(isNaN(v.length)) return funLen = true
    return acc + v.length
  }, 0)

  function lengthOf(part, value) {
    return part.length || part.dynamicLength(value)
  }

  function getLength(obj) {
    return reduce(parts, function (acc, part, k) {
      return acc + lengthOf(part, obj[k])
    }, 0)
  }

  return {
    encode: function encode (obj, b, offset) {
      if(!b)
        b = new Buffer(funLen ? getLength(obj) : length )
      offset = offset | 0
      var _offset = offset

      for(var k in parts) {
        parts[k].encode(obj[k], b, offset)
        offset += parts[k].encode.bytesWritten
//        offset += lengthOf(parts[k], obj[k])
      }
      encode.bytesWritten = offset - _offset
      return b
    },
    decode: function decode (buffer) {
      var obj = {}
      var offset = decode.bytesRead = 0
      for(var k in parts) {
        obj[k] = parts[k].decode(buffer, offset)
        offset += lengthOf(parts[k], obj[k]) | 0
        decode.bytesRead += parts[k].decode.bytesRead
      }
      return obj
    },
    length: funLen ? null : length,
    dynamicLength: getLength
  }
}

//exports.int8 = ;(function () {
//  function encode (value, buffer, offset) {
//    if(!buffer) return new Buffer([value & 0xff])
//    console.log('byte-set', value, buffer, offset)
//    buffer[offset] = value
//    return buffer
//  }
//
//  function decode (buffer, offset) {
//    return buffer[offset | 0]
//  }
//
//  decode.bytesRead = encode.bytesWritten = 1
//  return {
//    encode: encode,
//    decode: decode,
//    length: 1
//  }
//})()
//

function createNumber(type, len) {
  var read = Buffer.prototype['read' + type]
  var write = Buffer.prototype['write' + type]
  function encode (value, b, offset) {
    b = b || new Buffer(len)
    write.call(b, value, offset | 0)
    return b
  }
  function decode (buffer, offset) {
    return read.call(buffer, offset|0)
  }

  encode.bytesWritten = decode.bytesRead = len
  return {
    encode: encode,
    decode: decode,
    length: len
  }
}

exports.byte =
exports.int8 =
exports.Int8 =
exports.UInt8 = createNumber('UInt8', 1)

exports.UInt16BE = createNumber('UInt16BE', 1)
exports.UInt32BE = createNumber('UInt32BE', 4)
exports.FloatBE  = createNumber('FloatBE', 4)
exports.DoubleBE = createNumber('DoubleBE', 8)

exports.UInt16BE = createNumber('UInt16LE', 1)
exports.UInt32BE = createNumber('UInt32LE', 4)
exports.FloatBE  = createNumber('FloatLE', 4)
exports.DoubleBE = createNumber('DoubleLE', 8)

exports.UInt16 = exports.UInt16BE
exports.UInt32 = exports.UInt32BE
exports.Float = exports.FloatBE
exports.Double = exports.DoubleBE

exports.buffer =
exports.array = function (len) {

  function encode (value, b, offset) {
    //already encodes a buffer, so if there is no b just return.
    if(!b) return value
    value.copy(b, offset, 0, len)
    return b
  }
  function decode (buffer, offset) {
    return buffer.slice(offset, offset + len)
  }
  encode.bytesWritten = decode.bytesRead = len

  return {
    encode: encode,
    decode: decode,
    length: len
  }
}

exports.varbuf = function (lenType) {
  return {
    encode: function encode (value, buffer, offset) {
      buffer = buffer || new Buffer(this.dynamicLength(value) )
      offset = offset | 0
      buffer = lenType.encode(value.length, buffer, offset)
      offset += lenType.length || lenType.dynamicLength(value.length)
      value.copy(buffer, offset, 0, value.length)
      encode.bytesWritten = lenType.encode.bytesWritten + value.length
      return buffer
    },
    decode: function decode (buffer, offset) {
      offset = offset | 0
      var length = lenType.decode(buffer, offset)
      offset += lenType.length || lenType.dynamicLength(length)
      decode.bytesRead = lenType.decode.bytesRead + length
      return buffer.slice(offset, offset + length)
    },
    dynamicLength: function (value) {
      return value.length + (lenType.length || lenType.dynamicLength(value.length))
    }
  }
}

exports.varint = varint

exports.vararray = function (lenType, itemType) {
  return {
    encode: function encode (value, buffer, offset) {
      if(!Array.isArray(value))
        throw new Error('can only encode arrays')
      if(!buffer) {
        buffer = new Buffer(this.dynamicLength(value))
        offset = 0
      }
      var contentLength = value.length*itemType.length
      lenType.encode(contentLength, buffer, offset)
      offset += lenType.length || lenType.dynamicLength(contentLength)
      encode.bytesWritten = lenType.encode.bytesWritten
      value.forEach(function (e) {
        itemType.encode(e, buffer, offset)
        offset += itemType.length || itemType.dynamicLength(e)
        encode.bytesWritten += itemType.encode.bytesWritten
      })
      return buffer
    },
    decode: function decode (buffer, offset) {
      offset = offset | 0
      var length = lenType.decode(buffer, offset)
      offset += lenType.length || lenType.dynamicLength(length)
      o = offset
      decode.bytesRead = lenType.decode.bytesRead
      var array = []
      while(o < offset + length) {
        var last = itemType.decode(buffer, o)
        o += itemType.length || itemType.dynamicLength(last)
        decode.bytesRead += itemType.decode.bytesRead
        array.push(last)
      }
      return array
    },
    dynamicLength: function (value) {
      var contentLength = value.length*itemType.length
      return (
        contentLength
      + (lenType.length || lenType.dynamicLength(contentLength))
      )
    }
  }
}
<|endoftext|>


<commit_before>var self = require('sdk/self');

// a dummy function, to show how tests work.
// to see how to test this function, look at test/test-index.js
function dummy(text, callback) {
  callback(text);
}

exports.dummy = dummy;

var buttons = require('sdk/ui/button/action');
var tabs = require("sdk/tabs");

var button = buttons.ActionButton({
  id: "mozilla-link",
  label: "Visit Mozilla",
  icon: {
    "16": "./icon-16.png",
    "32": "./icon-32.png",
    "64": "./icon-64.png"
  },
  onClick: handleClick
});

function handleClick(state) {
	const { Panel } = require('sdk/panel');
	let panel = Panel({
		contentURL:require("sdk/self").data.url("panel.html"),
		width: 600,
		height: 600
	});
	panel.show();
}

<commit_msg>Agarrando panel no botao<commit_after>var self = require('sdk/self');

function dummy(text, callback) {
  callback(text);
}

exports.dummy = dummy;

var buttons = require('sdk/ui/button/action');
var tabs = require("sdk/tabs");

var button = buttons.ActionButton({
  id: "mozilla-link",
  label: "ADANADO",
  icon: {
    "16": "./icon-16.png",
    "32": "./icon-32.png",
    "64": "./icon-64.png"
  },
  onClick: handleChange
});

function handleChange(state) {
	const { Panel } = require('sdk/panel');
	let panel = Panel({
		contentURL:require("sdk/self").data.url("panel.html"),

	});
 panel.show({
      position: button
    });
}

<|endoftext|>


<commit_before>const fs = require('fs');
const path = require('path');

const test = false;

let files = test ? ['data-examples/example.in'] : [
  // 'data-examples/kittens.in',
  // 'data-examples/me_at_the_zoo.in',
  'data-examples/trending_today.in',
  // 'data-examples/videos_worth_spreading.in'
];

// V E R C X (example: 10000 1000 200000 500 6000)
//
// V - videos,
// E - endpoints,
// R - request descriptions
// C - cache servers
// X - capacity in mb of each server
//
// s2 videos sizes
// s3 endpoint {latency, cacheServersNum}
// s4 videoStats {requests, id, endpointId}

for (const fileName of files) {
  processFile(fileName);
}

function processFile(fileName) {
  fs.readFile(path.resolve(__dirname, fileName), 'utf8',
    /**
     * fs callback
     * @param  {Object} err
     * @param  {String} data
     */
    function(err, data) {
      if (err) {
        return console.log(err);
      }
      let dataArray = data.split('\n');
      if (dataArray[dataArray.length - 1] === '') {
        dataArray.pop(); // trim trailing whitespace
      }
      // console.log(dataArray);
      const firstLine = dataArray[0].split(' ');
      const V = parseInt(firstLine[0]);
      const E = parseInt(firstLine[1]);
      const R = parseInt(firstLine[2]);
      const C = parseInt(firstLine[3]);
      const X = parseInt(firstLine[4]);
      // console.log({
      //   V,
      //   E,
      //   R,
      //   C,
      //   X
      // });

      const videoSizes = dataArray[1].split(' ');
      // console.log(videoStats);

      dataArray.shift();
      dataArray.shift(); // to get data only
      // console.log(dataArray);

      const splittedData = dataSplitter(dataArray);

      // sort videos by requests
      let videos = splittedData.videoStats.sort(
        (a, b) => b.requests - a.requests
      );
      // add video size
      videos = videos.map((video, i) => {
        video.size = parseInt(videoSizes[videos[i].videoId]);
        return video;
      });
      // delete video with size bigger than X capacity
      videos = videos.filter(el => videoSizes[el.videoId] < X);
      // console.log('videos sorted and filtered');
      // console.log(videos);

      const servers = getCacheServersDistribution(V, E, R, C, X,
        videoSizes, splittedData, videos);
      const cacheServersNum = servers.length;

      let results = `${cacheServersNum}\n`;

      // console.log(servers);

      for (let i = 0; i < servers.length; i++) {
        results += `${servers[i].id}`;
        for (let j = 0; j < servers[i].videoIds.length; j++) {
          results += ` ${servers[i].videoIds[j]}`;
        }
        results += `\n`;
      }

      // example: output to file
      // 3 - how much cache servers
      // serverId videoId videoId
      // 0        2       3

      console.log(results);
      writeResults(results, fileName);
    }
  );
}

const getVideoId = (videos, videoId, endpointId) => {
  let result = -1;
  for (let i = 0; i < videos.length; i++) {
    if (videos[i].videoId === videoId &&
      videos[i].endpointId === endpointId) {
      result = i;
      break;
    }
  }
  return result;
};

const dataSplitter = (inputData = []) => {
  let result = {
    endpoints: [],
    videoStats: []
  };
  let data = Object.assign([], inputData);
  let currentEndpointId = 0;

  for (let i = 0; i < data.length; i++) {
    const line = data[i].split(' ');
    if (line.length === 2) {
      let latencies = parseInt(line[1]) ? getLatencies(data, i + 1, parseInt(line[1])) : [];
      // latencies = JSON.stringify(latencies);
      let endpoint = {
        id: currentEndpointId,
        latency: parseInt(line[0]),
        cacheServersNum: parseInt(line[1]),
        latencies: latencies
      };
      result.endpoints.push(endpoint);
      currentEndpointId++;
      i += endpoint.cacheServersNum;
      continue;
    }
    if (line.length === 3) {
      let video = {
        videoId: parseInt(line[0]),
        endpointId: parseInt(line[1]),
        requests: parseInt(line[2])
      };
      const existingVideoId = getVideoId(result.videoStats, video.videoId, video.endpointId);
      if (existingVideoId >= 0) {
        result.videoStats[existingVideoId].requests += video.requests;
      } else {
        result.videoStats.push(video);
      }
    }
  }

  // console.log(result);

  return result;
};

const getLatencies = (data, index, num) => {
  let result = [];
  for (let i = index; i < index + num; i++) {
    const cacheServerStats = data[i].split(' ');
    result.push({
      cacheServerId: parseInt(cacheServerStats[0]),
      latencyToTheCacheServer: parseInt(cacheServerStats[1])
    });
  }
  return result;
};

const checkCapacity = (videoSize, serverRemainingCapacity) => videoSize <= serverRemainingCapacity;
const checkIfVideoIsUsed = (videoIds, videoId) => videoIds.indexOf(videoId) < 0;

const checkIfEndpointFound = (endpoints, videoId) => {
  let result = false;
  for (let i = 0; i < endpoints.length; i++) {
    if (endpoints[i].endpointId === videoId) {
      result = true;
      break;
    }
  }
  return result;
};

const sharding = (cacheServersIn, videos) => {
  let cacheServers = Object.assign([], cacheServersIn);
  //fill servers, second step (sharding), check if video was already added!
  let sortedCacheServers = Object.assign([], cacheServers);
  for (let i = 0; i < sortedCacheServers.length; i++) {
    let cacheServer = sortedCacheServers[i];
    // console.log('cache serv: ', cacheServer);
    // console.log('videos: ', videos);
    for (let j = 0; j < videos.length; j++) {
      const videoObj = videos[j];
      // console.log('videosObj: ', videoObj); // with endpointId
      if (
        checkIfEndpointFound(cacheServer.endpoints, videoObj.endpointId) &&
        checkCapacity(videoObj.size, cacheServer.remainingCapacity) &&
        checkIfVideoIsUsed(cacheServer.videoIds, videoObj.videoId)
      ) {
        cacheServer.remainingCapacity -= videoObj.size;
        cacheServer.videoIds.push(videoObj.videoId);
      }
    }
  }
  return sortedCacheServers;
};

const findPriorityEndpointsLength = () => {
  let endpointsLength = 1;

  return endpointsLength;
};

const findPriorityVideosLength = (endpoint, videos) => {
  if (!videos || !videos.length) {
    return 0;
  }
  let videosLength = 1;
  let averageRequests = 0;
  let averageRequestsLength = 0;

  const filteredVideos = videos.filter(video => endpoint.endpointId === video.endpointId);

  for (let i = 0; i < filteredVideos.length; i++) {
    if (endpoint.endpointId === filteredVideos[i].endpointId) {
      averageRequests += filteredVideos[i].requests;
      averageRequestsLength++;
    }
  }
  averageRequests /= averageRequestsLength;
  // console.log({averageRequests, averageRequestsLength});

  for (let i = 1; i < filteredVideos.length; i++) {
    if (endpoint.endpointId === filteredVideos[i].endpointId &&
      filteredVideos[i].requests <= averageRequests) {
      videosLength = i;
      break;
    }
  }

  return videosLength;
};

const getCacheServersDistribution = (V, E, R, C, X,
  videoSizes, splittedData, videos) => {
  // add latency to server
  let tempServers = {};
  for (let j = 0; j < splittedData.endpoints.length; j++) {
    let latencies = Object.assign([], splittedData.endpoints[j].latencies);
    // console.log(latencies);
    for (let i = 0; i < latencies.length; i++) {
      if (!tempServers[latencies[i].cacheServerId]) {
        tempServers[latencies[i].cacheServerId] = {
          midLatency: 0,
          endpoints: []
        };
      }
      //count mid latency
      tempServers[latencies[i].cacheServerId].midLatency += latencies[i].latencyToTheCacheServer;

      // endpoints
      tempServers[latencies[i].cacheServerId].endpoints.push({
        endpointId: j,
        latency: latencies[i].latencyToTheCacheServer
      });
    }
    for (let i = 0; i < latencies.length; i++) {
      tempServers[latencies[i].cacheServerId].midLatency = tempServers[latencies[i].cacheServerId].midLatency / latencies.length;
    }
  }

  // console.log(tempServers);

  // sort by min mid latency
  let sortedCacheServers = [];
  for (let server in tempServers) {
    if (tempServers.hasOwnProperty(server)) {
      // console.log(tempServers);
      sortedCacheServers.push({
        remainingCapacity: X,
        id: parseInt(server),
        midLatency: parseInt(tempServers[server + ''].midLatency),
        endpoints: tempServers[server + ''].endpoints,
        videoIds: []
      });
    }
  }
  sortedCacheServers.sort((a, b) => a.midLatency - b.midLatency);
  // console.log(sortedCacheServers);

  // sort endpoints of each server by latency
  for (let i = 0; i < sortedCacheServers.length; i++) {
    sortedCacheServers[i].endpoints.sort((a, b) => a.latency - b.latency);
  }
  // console.log(sortedCacheServers);

  //fill em, first step (initial)
  for (let i = 0; i < sortedCacheServers.length; i++) {
    let cacheServer = sortedCacheServers[i];

    // let priorityEndpointsLength = 1;

    for (let k = 0; k < cacheServer.endpoints.length; k++) {
      const endpoint = cacheServer.endpoints[k];
      let priorityVideosLength = findPriorityVideosLength(endpoint, videos);
      // console.log('priorityVideosLength', priorityVideosLength);

      // console.log('sortedCacheServers[i]', cacheServer);

      //loop through videos to check if one of them have to be added
      for (let j = 0; j < videos.length; j++) {
        // console.log('serverRemainingCapacity', serverRemainingCapacity);
        const videoObj = videos[j];

        //when to start sharding
        if (priorityVideosLength === 0) {
          break;
        }

        //fill by minimal latency only, requires second step
        // console.log('videos[j]', videos[j]); // with endpointId
        if (videoObj.endpointId !== cacheServer.endpoints[0].endpointId) {
          continue;
        }

        //only one video with best latency
        if (
          checkCapacity(videoObj.size, cacheServer.remainingCapacity) &&
          checkIfVideoIsUsed(cacheServer.videoIds, videoObj.videoId)
        ) {
          cacheServer.remainingCapacity -= videoObj.size;
          cacheServer.videoIds.push(videoObj.videoId);
          priorityVideosLength--;
        }
      }
    }
  }

  //fill servers, second step (sharding), check if video was already added!
  sortedCacheServers = sharding(sortedCacheServers, videos);

  // console.log(JSON.stringify(sortedCacheServers));
  // console.log(sortedCacheServers);

  // clearup servers if no videos on it
  sortedCacheServers = sortedCacheServers.filter(server => server.videoIds.length);

  // console.log(sortedCacheServers);

  return sortedCacheServers;
};

/**
 * writeResults
 * @param  {String} data
 * @param  {String} fileName
 */
const writeResults = (data, fileName = '') => {
  fs.writeFile(`${fileName}.out.txt`, data, 'utf8', (err) => {
    if (err) {
      throw err;
    }
    console.log('Results were saved!');
  });
};
<commit_msg>added progress<commit_after>const fs = require('fs');
const path = require('path');

const test = false;

let files = test ? ['data-examples/example.in'] : [
  'data-examples/kittens.in',
  // 'data-examples/me_at_the_zoo.in',
  // 'data-examples/trending_today.in',
  // 'data-examples/videos_worth_spreading.in'
];

// V E R C X (example: 10000 1000 200000 500 6000)
//
// V - videos,
// E - endpoints,
// R - request descriptions
// C - cache servers
// X - capacity in mb of each server
//
// s2 videos sizes
// s3 endpoint {latency, cacheServersNum}
// s4 videoStats {requests, id, endpointId}

for (const fileName of files) {
  console.log(`Reading file: ${fileName}!`);
  processFile(fileName);
}

function processFile(fileName) {
  fs.readFile(path.resolve(__dirname, fileName), 'utf8',
    /**
     * fs callback
     * @param  {Object} err
     * @param  {String} data
     */
    function(err, data) {
      if (err) {
        return console.log(err);
      }

      console.log(`File opened: ${fileName}!`);

      let dataArray = data.split('\n');
      if (dataArray[dataArray.length - 1] === '') {
        dataArray.pop(); // trim trailing whitespace
      }
      // console.log(dataArray);
      const firstLine = dataArray[0].split(' ');
      const V = parseInt(firstLine[0]);
      const E = parseInt(firstLine[1]);
      const R = parseInt(firstLine[2]);
      const C = parseInt(firstLine[3]);
      const X = parseInt(firstLine[4]);
      // console.log({
      //   V,
      //   E,
      //   R,
      //   C,
      //   X
      // });

      const videoSizes = dataArray[1].split(' ');
      console.log('VideoSizes received!');
      // console.log(videoSizes);

      dataArray.shift();
      dataArray.shift(); // to get data only
      // console.log(dataArray);

      const splittedData = dataSplitter(dataArray);
      console.log('Data splitted!');

      // sort videos by requests
      let videos = splittedData.videoStats.sort(
        (a, b) => b.requests - a.requests
      );
      // add video size
      videos = videos.map((video, i) => {
        video.size = parseInt(videoSizes[videos[i].videoId]);
        return video;
      });
      // delete video with size bigger than X capacity
      videos = videos.filter(el => videoSizes[el.videoId] < X);
      console.log('Videos sorted and filtered');
      // console.log(videos);

      const servers = getCacheServersDistribution(V, E, R, C, X,
        videoSizes, splittedData, videos);
      const cacheServersNum = servers.length;

      let results = `${cacheServersNum}\n`;

      // console.log(servers);

      for (let i = 0; i < servers.length; i++) {
        results += `${servers[i].id}`;
        for (let j = 0; j < servers[i].videoIds.length; j++) {
          results += ` ${servers[i].videoIds[j]}`;
        }
        results += `\n`;
      }

      // example: output to file
      // 3 - how much cache servers
      // serverId videoId videoId
      // 0        2       3

      console.log(results);
      writeResults(results, fileName);
    }
  );
}

const getVideoId = (videos, videoId, endpointId) => {
  let result = -1;
  for (let i = 0; i < videos.length; i++) {
    if (videos[i].videoId === videoId &&
      videos[i].endpointId === endpointId) {
      result = i;
      break;
    }
  }
  return result;
};

const dataSplitter = (inputData = []) => {
  let result = {
    endpoints: [],
    videoStats: []
  };
  let data = Object.assign([], inputData);
  let currentEndpointId = 0;

  for (let i = 0; i < data.length; i++) {
    const line = data[i].split(' ');
    if (line.length === 2) {
      let latencies = parseInt(line[1]) ? getLatencies(data, i + 1, parseInt(line[1])) : [];
      // latencies = JSON.stringify(latencies);
      let endpoint = {
        id: currentEndpointId,
        latency: parseInt(line[0]),
        cacheServersNum: parseInt(line[1]),
        latencies: latencies
      };
      result.endpoints.push(endpoint);
      currentEndpointId++;
      i += endpoint.cacheServersNum;
      continue;
    }
    if (line.length === 3) {
      let video = {
        videoId: parseInt(line[0]),
        endpointId: parseInt(line[1]),
        requests: parseInt(line[2])
      };
      const existingVideoId = getVideoId(result.videoStats, video.videoId, video.endpointId);
      if (existingVideoId >= 0) {
        result.videoStats[existingVideoId].requests += video.requests;
      } else {
        result.videoStats.push(video);
      }
    }
  }

  // console.log(result);

  return result;
};

const getLatencies = (data, index, num) => {
  let result = [];
  for (let i = index; i < index + num; i++) {
    const cacheServerStats = data[i].split(' ');
    result.push({
      cacheServerId: parseInt(cacheServerStats[0]),
      latencyToTheCacheServer: parseInt(cacheServerStats[1])
    });
  }
  return result;
};

const checkCapacity = (videoSize, serverRemainingCapacity) => videoSize <= serverRemainingCapacity;
const checkIfVideoIsUsed = (videoIds, videoId) => videoIds.indexOf(videoId) < 0;

const checkIfEndpointFound = (endpoints, videoId) => {
  let result = false;
  for (let i = 0; i < endpoints.length; i++) {
    if (endpoints[i].endpointId === videoId) {
      result = true;
      break;
    }
  }
  return result;
};

const sharding = (cacheServersIn, videos) => {
  let cacheServers = Object.assign([], cacheServersIn);
  //fill servers, second step (sharding), check if video was already added!
  let sortedCacheServers = Object.assign([], cacheServers);
  for (let i = 0; i < sortedCacheServers.length; i++) {
    let cacheServer = sortedCacheServers[i];
    // console.log('cache serv: ', cacheServer);
    // console.log('videos: ', videos);
    for (let j = 0; j < videos.length; j++) {
      const videoObj = videos[j];
      // console.log('videosObj: ', videoObj); // with endpointId
      if (
        checkIfEndpointFound(cacheServer.endpoints, videoObj.endpointId) &&
        checkCapacity(videoObj.size, cacheServer.remainingCapacity) &&
        checkIfVideoIsUsed(cacheServer.videoIds, videoObj.videoId)
      ) {
        cacheServer.remainingCapacity -= videoObj.size;
        cacheServer.videoIds.push(videoObj.videoId);
      }
    }
  }
  return sortedCacheServers;
};

const findPriorityEndpointsLength = () => {
  let endpointsLength = 1;

  return endpointsLength;
};

const findPriorityVideosLength = (endpoint, videos) => {
  if (!videos || !videos.length) {
    return 0;
  }
  let videosLength = 1;
  let averageRequests = 0;
  let averageRequestsLength = 0;

  const filteredVideos = videos.filter(video => endpoint.endpointId === video.endpointId);

  for (let i = 0; i < filteredVideos.length; i++) {
    if (endpoint.endpointId === filteredVideos[i].endpointId) {
      averageRequests += filteredVideos[i].requests;
      averageRequestsLength++;
    }
  }
  averageRequests /= averageRequestsLength;
  // console.log({averageRequests, averageRequestsLength});

  for (let i = 1; i < filteredVideos.length; i++) {
    if (endpoint.endpointId === filteredVideos[i].endpointId &&
      filteredVideos[i].requests <= averageRequests) {
      videosLength = i;
      break;
    }
  }

  return videosLength;
};

const getCacheServersDistribution = (V, E, R, C, X,
  videoSizes, splittedData, videos) => {
  // add latency to server
  let tempServers = {};
  for (let j = 0; j < splittedData.endpoints.length; j++) {
    let latencies = Object.assign([], splittedData.endpoints[j].latencies);
    // console.log(latencies);
    for (let i = 0; i < latencies.length; i++) {
      if (!tempServers[latencies[i].cacheServerId]) {
        tempServers[latencies[i].cacheServerId] = {
          midLatency: 0,
          endpoints: []
        };
      }
      //count mid latency
      tempServers[latencies[i].cacheServerId].midLatency += latencies[i].latencyToTheCacheServer;

      // endpoints
      tempServers[latencies[i].cacheServerId].endpoints.push({
        endpointId: j,
        latency: latencies[i].latencyToTheCacheServer
      });
    }
    for (let i = 0; i < latencies.length; i++) {
      tempServers[latencies[i].cacheServerId].midLatency = tempServers[latencies[i].cacheServerId].midLatency / latencies.length;
    }
  }

  // console.log(tempServers);

  console.log('Servers gathered with data!');

  // sort by min mid latency
  let sortedCacheServers = [];
  for (let server in tempServers) {
    if (tempServers.hasOwnProperty(server)) {
      // console.log(tempServers);
      sortedCacheServers.push({
        remainingCapacity: X,
        id: parseInt(server),
        midLatency: parseInt(tempServers[server + ''].midLatency),
        endpoints: tempServers[server + ''].endpoints,
        videoIds: []
      });
    }
  }
  sortedCacheServers.sort((a, b) => a.midLatency - b.midLatency);
  // console.log(sortedCacheServers);

  // sort endpoints of each server by latency
  for (let i = 0; i < sortedCacheServers.length; i++) {
    sortedCacheServers[i].endpoints.sort((a, b) => a.latency - b.latency);
  }
  // console.log(sortedCacheServers);

  console.log(`Servers (${sortedCacheServers.length}) sorted and filled with endpoints`);

  //fill em, first step (initial)
  for (let i = 0; i < sortedCacheServers.length; i++) {
    console.log(`Working with server ${i}`);

    let cacheServer = sortedCacheServers[i];

    // let priorityEndpointsLength = 1;

    for (let k = 0; k < cacheServer.endpoints.length; k++) {
      const endpoint = cacheServer.endpoints[k];
      let priorityVideosLength = findPriorityVideosLength(endpoint, videos);
      // console.log('priorityVideosLength', priorityVideosLength);

      // console.log('sortedCacheServers[i]', cacheServer);

      //loop through videos to check if one of them have to be added
      for (let j = 0; j < videos.length; j++) {
        // console.log('serverRemainingCapacity', serverRemainingCapacity);
        const videoObj = videos[j];

        //when to start sharding
        if (priorityVideosLength === 0) {
          break;
        }

        //fill by minimal latency only, requires second step
        // console.log('videos[j]', videos[j]); // with endpointId
        if (videoObj.endpointId !== cacheServer.endpoints[0].endpointId) {
          continue;
        }

        //only one video with best latency
        if (
          checkCapacity(videoObj.size, cacheServer.remainingCapacity) &&
          checkIfVideoIsUsed(cacheServer.videoIds, videoObj.videoId)
        ) {
          cacheServer.remainingCapacity -= videoObj.size;
          cacheServer.videoIds.push(videoObj.videoId);
          priorityVideosLength--;
        }
      }
    }
  }

  //fill servers, second step (sharding), check if video was already added!
  sortedCacheServers = sharding(sortedCacheServers, videos);

  console.log('Servers sharded');

  // console.log(JSON.stringify(sortedCacheServers));
  // console.log(sortedCacheServers);

  // clearup servers if no videos on it
  sortedCacheServers = sortedCacheServers.filter(server => server.videoIds.length);

  // console.log(sortedCacheServers);

  return sortedCacheServers;
};

/**
 * writeResults
 * @param  {String} data
 * @param  {String} fileName
 */
const writeResults = (data, fileName = '') => {
  fs.writeFile(`${fileName}.out.txt`, data, 'utf8', (err) => {
    if (err) {
      throw err;
    }
    console.log('Results were saved!');
  });
};
<|endoftext|>


<commit_before>var _ = require('lodash');
var request = require('request');

var ENDPOINT = 'http://api.captainteemo.com';
var PLATFORMS = [
  {'short': 'na', 'full': 'North_America'},
  {'short': 'br', 'full': 'Brasil'},
  {'short': 'ru', 'full': 'Russia'},
  {'short': 'euw', 'full': 'Europe_West'},
  {'short': 'eun', 'full': 'Europe_East'},
  {'short': 'tr', 'full': 'Turkey'},
  {'short': 'las', 'full': 'Latin_America_South'},
  {'short': 'lan', 'full': 'Latin_America_North'}
];

/**
 * Create an error whose message begins with 'node-resteemo - '.
 *
 * @private
 * @param {String} value The error message.
 * @return {Error}
 */
function brandError(value) {
  return new Error('node-resteemo - ' + value);
}

/**
 * Converts a full platform string to its shorthand equivalent.
 *
 * @private
 * @param {String} platform The platform to shorten.
 * @return {String} The shorthand equivalent of `platform` if one exists, else
 *   `null`.
 */
function normalizePlatform(platform) {
  var matchFromShortPlatform = _.find(PLATFORMS, {'short': platform});
  if (!_.isUndefined(matchFromShortPlatform)) {
    return platform;
  }

  var matchFromFullPlatform = _.find(PLATFORMS, {'full': platform});
  if (!_.isUndefined(matchFromFullPlatform)) {
    return matchFromFullPlatform['short'];
  }

  return null;
}

/**
 * Parse JSON data from a request.
 *
 * @private
 * @param {Object} req The request instance to handle.
 * @param {Function} callback The callback, which is given two arguments:
 *   `(err, response)`, where `response` is a JSON Object from `req`.
 */
function responseHandler(req, callback) {
  req.on('response', function(res) {
    var response = '';
    res.setEncoding('utf8');
    res.on('data', function(chunk) {
      response += chunk;
    });
    res.on('end', function() {
      try {
        response = JSON.parse(response);
      }
      catch(e) {
        var error = brandError('invalid json response');
        return callback(error);
      }

      if (!response.success) {
        var error = brandError('api failed');
        return callback(error);
      }

      var hasSecondSuccess;
      switch (false) {
        case !(_.contains(res.request.uri.path, 'recent_games')):
          hasSecondSuccess = true;
          break;
        case !(_.contains(res.request.uri.path, 'runes')):
          hasSecondSuccess = true;
          break;
        case !(_.contains(res.request.uri.path, 'mastery')):
          hasSecondSuccess = true;
          break;
        case !(_.contains(res.request.uri.path, 'leagues')):
          hasSecondSuccess = true;
          break;
        default:
          hasSecondSuccess = false;
      }

      if (hasSecondSuccess) {
        if (!response.data._success) {
          var error = brandError('api failed at second success check');
          return callback(error);
        }
      }

      callback(null, response);
    });
  });
}

/**
 * Establishes the user agent of all API calls.
 *
 * @param {String} refererString The referer string to use in API calls.
 * @return {Object}
 */
module.exports = function(refererString) {
  if (!_.isString(refererString)) {
    var error = brandError('`refererString` not defined');
    throw error;
  }

  /**
   * Contructs the headers and options for the API request.
   *
   * @private
   * @param {String} method The HTTP verb.
   * @param {String} path The path to query from the API endpoint.
   * @param {Function} callback The callback, which is passed to
   *   `responseHandler`.
   */
  function prepareRequest(method, path, callback) {
    if (!_.isFunction(callback)) {
      var error = brandError('missing callback');
      throw error;
    }

    var headers = {
      'Accept'    : 'application/json',
      'User-Agent': refererString
    };

    var requestOptions = {
      method : method,
      uri    : ENDPOINT + path,
      headers: headers
    };

    var req = request(requestOptions);
    responseHandler(req, callback);
    req.end();
  }

  /**
   * Begins the preparation for a GET request.
   *
   * @private
   * @param {String} path The path to query from the API endpoint.
   * @param {Function} callback The callback, which is passed to
   *   `prepareRequest`.
   */
  function get(path, callback) {
    prepareRequest('GET', path, callback);
  }

  /**
   * Sets up a player request.
   *
   * @private
   * @param {String} platform
   * @param {String} summoner
   * @param {String} path
   * @param {Function} callback
   */
  function playerRequest(options, callback) {
    var shortPlatform = normalizePlatform(options.platform);
    if (_.isNull(shortPlatform)) {
      var error = brandError('invalid platform');
      return callback(error);
    }

    if (_.isNull(options.path)) {
      options.path = '';
    } else {
      options.path = '/' + options.path;
    }

    if (_.isUndefined(options.season)) {
      get('/player/' + shortPlatform + '/' + options.summoner + options.path, callback);
    } else {
      get('/player/' + shortPlatform + '/' + options.summoner + options.path + '/' + options.season, callback);
    }
  }

  var teemo = {};

  /**
   * Returns primarily ID-based data for String `summoner` on String `platform`.
   * Account and summoner IDs are not unique across multiple platforms.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Function} callback Used as `callback(error, profile)` where
   *   `profile` is the API response as an Object.
   */
  teemo.player = function(platform, summoner, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: null
    }, callback);
  };

  /**
   * Returns last 10 matches (order is random) for String `summoner` on String
   * `platform`.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Function} callback Used as `callback(error, games)` where `games`
   *   is the API response as an Object.
   */
  teemo.player.recentGames = function(platform, summoner, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: 'recent_games'
    }, callback);
  };

  /**
   * Returns lifetime influence point gains for String `summoner` on String
   * `platform`.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Function} callback Used as `callback(error, points)` where `points`
   *   is the API response as an Object.
   */
  teemo.player.influencePoints = function(platform, summoner, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: 'influence_points'
    }, callback);
  };

  /**
   * Returns runepages for String `summoner` on String `platform`.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Function} callback Used as `callback(error, runes)` where `runes`
   *   is the API response as an Object.
   */
  teemo.player.runes = function(platform, summoner, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: 'runes'
    }, callback);
  };

  /**
   * Returns mastery pages for String `summoner` on String `platform`.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Function} callback Used as `callback(error, pages)` where `pages`
   *   is the API response as an Object.
   */
  teemo.player.mastery = function(platform, summoner, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: 'mastery'
    }, callback);
  };

  /**
   * Returns Season 3 Leagues info.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Function} callback Used as `callback(error, leagues)` where
   *   `leagues` is the API response as an Object.
   */
  teemo.player.leagues = function(platform, summoner, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: 'leagues'
    }, callback);
  };

  /**
   * Returns honor.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Function} callback Used as `callback(error, honor)` where `honor`
   *   is the API response as an Object.
   */
  teemo.player.honor = function(platform, summoner, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: 'honor'
    }, callback);
  };

  /**
   * Returns ranked stats for String `summoner` in Number `season`.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Number} season
   * @param {Function} callback Used as `callback(error, stats)` where `stats`
   *   is the API response as an Object.
   */
  teemo.player.rankedStats = function(platform, summoner, season, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: 'ranked_stats/season',
      season: season
    }, callback);
  };

  return teemo;
};<commit_msg>Simplify check for a season option<commit_after>var _ = require('lodash');
var request = require('request');

var ENDPOINT = 'http://api.captainteemo.com';
var PLATFORMS = [
  {'short': 'na', 'full': 'North_America'},
  {'short': 'br', 'full': 'Brasil'},
  {'short': 'ru', 'full': 'Russia'},
  {'short': 'euw', 'full': 'Europe_West'},
  {'short': 'eun', 'full': 'Europe_East'},
  {'short': 'tr', 'full': 'Turkey'},
  {'short': 'las', 'full': 'Latin_America_South'},
  {'short': 'lan', 'full': 'Latin_America_North'}
];

/**
 * Create an error whose message begins with 'node-resteemo - '.
 *
 * @private
 * @param {String} value The error message.
 * @return {Error}
 */
function brandError(value) {
  return new Error('node-resteemo - ' + value);
}

/**
 * Converts a full platform string to its shorthand equivalent.
 *
 * @private
 * @param {String} platform The platform to shorten.
 * @return {String} The shorthand equivalent of `platform` if one exists, else
 *   `null`.
 */
function normalizePlatform(platform) {
  var matchFromShortPlatform = _.find(PLATFORMS, {'short': platform});
  if (!_.isUndefined(matchFromShortPlatform)) {
    return platform;
  }

  var matchFromFullPlatform = _.find(PLATFORMS, {'full': platform});
  if (!_.isUndefined(matchFromFullPlatform)) {
    return matchFromFullPlatform['short'];
  }

  return null;
}

/**
 * Parse JSON data from a request.
 *
 * @private
 * @param {Object} req The request instance to handle.
 * @param {Function} callback The callback, which is given two arguments:
 *   `(err, response)`, where `response` is a JSON Object from `req`.
 */
function responseHandler(req, callback) {
  req.on('response', function(res) {
    var response = '';
    res.setEncoding('utf8');
    res.on('data', function(chunk) {
      response += chunk;
    });
    res.on('end', function() {
      try {
        response = JSON.parse(response);
      }
      catch(e) {
        var error = brandError('invalid json response');
        return callback(error);
      }

      if (!response.success) {
        var error = brandError('api failed');
        return callback(error);
      }

      var hasSecondSuccess;
      switch (false) {
        case !(_.contains(res.request.uri.path, 'recent_games')):
          hasSecondSuccess = true;
          break;
        case !(_.contains(res.request.uri.path, 'runes')):
          hasSecondSuccess = true;
          break;
        case !(_.contains(res.request.uri.path, 'mastery')):
          hasSecondSuccess = true;
          break;
        case !(_.contains(res.request.uri.path, 'leagues')):
          hasSecondSuccess = true;
          break;
        default:
          hasSecondSuccess = false;
      }

      if (hasSecondSuccess) {
        if (!response.data._success) {
          var error = brandError('api failed at second success check');
          return callback(error);
        }
      }

      callback(null, response);
    });
  });
}

/**
 * Establishes the user agent of all API calls.
 *
 * @param {String} refererString The referer string to use in API calls.
 * @return {Object}
 */
module.exports = function(refererString) {
  if (!_.isString(refererString)) {
    var error = brandError('`refererString` not defined');
    throw error;
  }

  /**
   * Contructs the headers and options for the API request.
   *
   * @private
   * @param {String} method The HTTP verb.
   * @param {String} path The path to query from the API endpoint.
   * @param {Function} callback The callback, which is passed to
   *   `responseHandler`.
   */
  function prepareRequest(method, path, callback) {
    if (!_.isFunction(callback)) {
      var error = brandError('missing callback');
      throw error;
    }

    var headers = {
      'Accept'    : 'application/json',
      'User-Agent': refererString
    };

    var requestOptions = {
      method : method,
      uri    : ENDPOINT + path,
      headers: headers
    };

    var req = request(requestOptions);
    responseHandler(req, callback);
    req.end();
  }

  /**
   * Begins the preparation for a GET request.
   *
   * @private
   * @param {String} path The path to query from the API endpoint.
   * @param {Function} callback The callback, which is passed to
   *   `prepareRequest`.
   */
  function get(path, callback) {
    prepareRequest('GET', path, callback);
  }

  /**
   * Sets up a player request.
   *
   * @private
   * @param {String} platform
   * @param {String} summoner
   * @param {String} path
   * @param {Function} callback
   */
  function playerRequest(options, callback) {
    var shortPlatform = normalizePlatform(options.platform);
    if (_.isNull(shortPlatform)) {
      var error = brandError('invalid platform');
      return callback(error);
    }

    if (_.isNull(options.path)) {
      options.path = '';
    } else {
      options.path = '/' + options.path;
    }

    get('/player/' + shortPlatform + '/' + options.summoner + options.path + (options.season || ''), callback);
  }

  var teemo = {};

  /**
   * Returns primarily ID-based data for String `summoner` on String `platform`.
   * Account and summoner IDs are not unique across multiple platforms.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Function} callback Used as `callback(error, profile)` where
   *   `profile` is the API response as an Object.
   */
  teemo.player = function(platform, summoner, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: null
    }, callback);
  };

  /**
   * Returns last 10 matches (order is random) for String `summoner` on String
   * `platform`.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Function} callback Used as `callback(error, games)` where `games`
   *   is the API response as an Object.
   */
  teemo.player.recentGames = function(platform, summoner, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: 'recent_games'
    }, callback);
  };

  /**
   * Returns lifetime influence point gains for String `summoner` on String
   * `platform`.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Function} callback Used as `callback(error, points)` where `points`
   *   is the API response as an Object.
   */
  teemo.player.influencePoints = function(platform, summoner, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: 'influence_points'
    }, callback);
  };

  /**
   * Returns runepages for String `summoner` on String `platform`.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Function} callback Used as `callback(error, runes)` where `runes`
   *   is the API response as an Object.
   */
  teemo.player.runes = function(platform, summoner, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: 'runes'
    }, callback);
  };

  /**
   * Returns mastery pages for String `summoner` on String `platform`.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Function} callback Used as `callback(error, pages)` where `pages`
   *   is the API response as an Object.
   */
  teemo.player.mastery = function(platform, summoner, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: 'mastery'
    }, callback);
  };

  /**
   * Returns Season 3 Leagues info.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Function} callback Used as `callback(error, leagues)` where
   *   `leagues` is the API response as an Object.
   */
  teemo.player.leagues = function(platform, summoner, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: 'leagues'
    }, callback);
  };

  /**
   * Returns honor.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Function} callback Used as `callback(error, honor)` where `honor`
   *   is the API response as an Object.
   */
  teemo.player.honor = function(platform, summoner, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: 'honor'
    }, callback);
  };

  /**
   * Returns ranked stats for String `summoner` in Number `season`.
   *
   * @public
   * @param {String} platform
   * @param {String} summoner
   * @param {Number} season
   * @param {Function} callback Used as `callback(error, stats)` where `stats`
   *   is the API response as an Object.
   */
  teemo.player.rankedStats = function(platform, summoner, season, callback) {
    playerRequest({
      platform: platform,
      summoner: summoner,
      path: 'ranked_stats/season/',
      season: season
    }, callback);
  };

  return teemo;
};<|endoftext|>


<commit_before>/*jslint -W079 */
/*jslint node: true */
'use strict';
var EventEmitter = require('events').EventEmitter;
var inherits = require('util').inherits;
var Stream = require('stream').Stream;

var WebSocket = require('ws');
var Guid = require('guid');

function GremlinClient(port, host) {
  this.port = port || 8182;
  this.host = host || 'localhost';

  this.connected = false;
  this.queue = [];

  this.commands = {};

  // Open websocket connection
  this.ws = new WebSocket('ws://'+ this.host +':'+ this.port);

  this.ws.on('open', this.onConnect.bind(this));

  this.ws.onerror = function(e) {
    console.log("Error:", e);
  };

  this.ws.on('message', this.handleMessage.bind(this));

  this.ws.on('close', this.onClose.bind(this));
}

inherits(GremlinClient, EventEmitter);

GremlinClient.prototype.handleMessage = function(data, flags) {
  var message = JSON.parse(data);
  var command = this.commands[message.requestId];

  if (message.type === 0) {
    message.result = command.result;
    return command.onEnd(message);
  }

  if (message.type === 1) {
    command.onData(message);
  }
};

GremlinClient.prototype.onConnect = function() {
  this.connected = true;
  this.emit('connect');

  this.executeQueue();
};

GremlinClient.prototype.onClose = function() {
  console.log("WebSocket closed");
};

GremlinClient.prototype.executeQueue = function() {
  var command;

  while (this.queue.length > 0) {
    command = this.queue.shift();
    this.send_message(command);
  }
};

GremlinClient.prototype.queueCommand = function(job) {
  var guid = Guid.create().value;
  var command = {
    message: {
      requestId: guid,
      processor: "",
      op: "eval",
      args: {
        gremlin: job.script,
        accept: "application/json"
      }
    },
    onData: job.onData,
    onEnd: job.onEnd,
    result: []
  };

  this.commands[guid] = command;

  return command;
};

GremlinClient.prototype.send_message = function(command) {
  this.ws.send(JSON.stringify(command.message));
};

GremlinClient.prototype.execute = function(script, callback) {
  var command = this.queueCommand({
    script: script,
    onData: function(message) {
      this.result = this.result.concat(message.result);
    },
    onEnd: function(data) {
      return callback(null, data);
    }
  });

  if (this.connected) {
    this.send_message(command);
  } else {
    this.queue.push(command);
  }
};

GremlinClient.prototype.stream = function(script) {
  var stream = new Stream();

  var command = this.queueCommand({
    script: script,
    onData: function(data) {
      stream.emit('data', data);
      stream.emit('result', data.result, data);
    },
    onEnd: function(data) {
      stream.emit('end', data);
    }
  });

  if (this.connected) {
    this.send_message(command);
  } else {
    this.queue.push(command);
  }

  return stream;
};

module.exports.createClient = function (port, host) {
  return new GremlinClient();
};<commit_msg>Switch to browser WebSocket API<commit_after>/*jslint -W079 */
/*jslint node: true */
'use strict';
var EventEmitter = require('events').EventEmitter;
var inherits = require('util').inherits;
var Stream = require('stream').Stream;

var WebSocket = require('ws');
var Guid = require('guid');

function GremlinClient(port, host) {
  this.port = port || 8182;
  this.host = host || 'localhost';

  this.connected = false;
  this.queue = [];

  this.commands = {};

  // Open websocket connection
  this.ws = new WebSocket('ws://'+ this.host +':'+ this.port);

  this.ws.onopen = this.onOpen.bind(this);

  this.ws.onerror = function(e) {
    console.log("Error:", e);
  };

  this.ws.onmessage = this.onMessage.bind(this);

  this.ws.onclose = this.onClose.bind(this);
}

inherits(GremlinClient, EventEmitter);

GremlinClient.prototype.onMessage = function(data, flags) {
  var message = JSON.parse(data.data || data);
  var command = this.commands[message.requestId];

  if (message.type === 0) {
    message.result = command.result;
    return command.onEnd(message);
  }

  if (message.type === 1) {
    command.onData(message);
  }
};

GremlinClient.prototype.onOpen = function() {
  this.connected = true;
  this.emit('connect');

  this.executeQueue();
};

GremlinClient.prototype.onClose = function(code) {
  console.log("WebSocket closed", code);
};

GremlinClient.prototype.executeQueue = function() {
  var command;

  while (this.queue.length > 0) {
    command = this.queue.shift();
    this.send_message(command);
  }
};

GremlinClient.prototype.queueCommand = function(job) {
  var guid = Guid.create().value;
  var command = {
    message: {
      requestId: guid,
      processor: "",
      op: "eval",
      args: {
        gremlin: job.script,
        accept: "application/json"
      }
    },
    onData: job.onData,
    onEnd: job.onEnd,
    result: []
  };

  this.commands[guid] = command;

  return command;
};

GremlinClient.prototype.send_message = function(command) {
  this.ws.send(JSON.stringify(command.message));
};

GremlinClient.prototype.execute = function(script, callback) {
  var command = this.queueCommand({
    script: script,
    onData: function(message) {
      this.result = this.result.concat(message.result);
    },
    onEnd: function(data) {
      return callback(null, data);
    }
  });

  if (this.connected) {
    this.send_message(command);
  } else {
    this.queue.push(command);
  }
};

GremlinClient.prototype.stream = function(script) {
  var stream = new Stream();

  var command = this.queueCommand({
    script: script,
    onData: function(data) {
      stream.emit('data', data);
      stream.emit('result', data.result, data);
    },
    onEnd: function(data) {
      stream.emit('end', data);
    }
  });

  if (this.connected) {
    this.send_message(command);
  } else {
    this.queue.push(command);
  }

  return stream;
};

module.exports.createClient = function (port, host) {
  return new GremlinClient();
};<|endoftext|>