From 5c5dbbbb1b5d727f98f67be7358506edb34b99a9 Mon Sep 17 00:00:00 2001 From: flytoj2ee Date: Tue, 25 Nov 2014 22:33:06 +0800 Subject: [PATCH] PoC Assembly - TC API - Generate Source Code Image API --- actions/sourceCodeImage.js | 122 + apiary.apib | 62 + config/tc-config.js | 14 +- deploy/ci.sh | 12 +- deploy/development.bat | 12 +- deploy/development.sh | 12 +- package.json | 4 +- routes.js | 10 +- test/test.sourceCodeImage.js | 155 + test/test_files/sourceCodeImage/c++.txt | 66 + test/test_files/sourceCodeImage/cs.txt | 24 + test/test_files/sourceCodeImage/java-100k.txt | 3447 +++++++++++++++++ test/test_files/sourceCodeImage/java-10k.txt | 345 ++ test/test_files/sourceCodeImage/java-30k.txt | 1007 +++++ test/test_files/sourceCodeImage/java-60k.txt | 1999 ++++++++++ test/test_files/sourceCodeImage/java.txt | 14 + test/test_files/sourceCodeImage/python.txt | 8 + test/test_files/sourceCodeImage/vbnet.txt | 23 + 18 files changed, 7325 insertions(+), 11 deletions(-) create mode 100644 actions/sourceCodeImage.js create mode 100644 test/test.sourceCodeImage.js create mode 100644 test/test_files/sourceCodeImage/c++.txt create mode 100644 test/test_files/sourceCodeImage/cs.txt create mode 100644 test/test_files/sourceCodeImage/java-100k.txt create mode 100644 test/test_files/sourceCodeImage/java-10k.txt create mode 100644 test/test_files/sourceCodeImage/java-30k.txt create mode 100644 test/test_files/sourceCodeImage/java-60k.txt create mode 100644 test/test_files/sourceCodeImage/java.txt create mode 100644 test/test_files/sourceCodeImage/python.txt create mode 100644 test/test_files/sourceCodeImage/vbnet.txt diff --git a/actions/sourceCodeImage.js b/actions/sourceCodeImage.js new file mode 100644 index 000000000..1296e0890 --- /dev/null +++ b/actions/sourceCodeImage.js @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2014 TopCoder Inc., All Rights Reserved. + * + * The api to convert the source code to image. + * + * @version 1.0 + * @author TCASSEMBLER + */ +/*jslint node: true, nomen: true, plusplus: true, unparam: true */ +"use strict"; +var async = require('async'); +var _ = require('underscore'); +var IllegalArgumentError = require('../errors/IllegalArgumentError'); +var highlight = require('highlight.js'); +var wkhtmltoimage = require('wkhtmltoimage'); +var BadRequestError = require('../errors/BadRequestError'); +var exec = require('child_process').exec; + +// The style name array. +var STYLE_NAMES = ['arta', 'ascetic', 'atelier-dune.dark', 'atelier-dune.light', 'atelier-forest.dark', 'atelier-forest.light', + 'atelier-heath.dark', 'atelier-heath.light', 'atelier-lakeside.dark', 'atelier-lakeside.light', 'atelier-seaside.dark', + 'atelier-seaside.light', 'brown_paper', 'codepen-embed', 'color-brewer', 'dark', 'default', 'docco', + 'far', 'foundation', 'github', 'googlecode', 'hybrid', 'idea', 'ir_black', 'kimbie.dark', 'kimbie.light', 'magula', + 'mono-blue', 'monokai', 'monokai_sublime', 'obsidian', 'paraiso.dark', 'paraiso.light', 'pojoaque', 'railscasts', + 'rainbow', 'school_book', 'solarized_dark', 'solarized_light', 'sunburst', 'tomorrow-night-blue', + 'tomorrow-night-bright', 'tomorrow-night-eighties', 'tomorrow-night', 'tomorrow', 'vs', 'xcode', 'zenburn']; + +/** + * Convert the source code to image. + * + * @param api - the api instance + * @param connection - the request connection instance + * @param next - the callback method. + */ +var convertSourceCodeToImage = function (api, connection, next) { + var helper = api.helper, + highlightResult = '', + emptyStr = '', + code = connection.params.code + emptyStr, + style = connection.params.style, + language = connection.params.lang; + + async.waterfall([ + function (cb) { + if (_.isNull(language) || _.isEmpty(language) || !highlight.getLanguage(language + emptyStr)) { + cb(new IllegalArgumentError("The language name is invalid.")); + return; + } + + if (!_.isUndefined(style) && !_.isNull(style) && !_.isEmpty(style) && !_.contains(STYLE_NAMES, style + emptyStr)) { + cb(new IllegalArgumentError("The style name is invalid.")); + return; + } + + exec(api.config.tcConfig.generateSourceCodeImage.wkhtmltoimageCommandPath + ' -H', function(error, stdout, stderr) { + if (stderr !== null && stderr !== '') { + cb(new IllegalArgumentError('The wkhtmltoimageCommandPath in configuration is invalid. The return error is ' + stderr)); + return; + } + cb(); + }); + }, function (cb) { + var styleLink = api.config.tcConfig.generateSourceCodeImage.styleLink; + if (!_.isUndefined(style) && !_.isNull(language) && !_.isEmpty(language)) { + styleLink = styleLink.replace('%OVERRIDE_STYLE_NAME%', style); + } else { + styleLink = styleLink.replace('%OVERRIDE_STYLE_NAME%', 'default'); + } + highlight.configure({ 'useBR': true }); + highlightResult = highlight.highlight(language, code, true).value; + highlightResult = '
' + highlightResult;
+            highlightResult = highlightResult + '
'; + cb(); + }, function (cb) { + var response = connection.rawConnection.res, + tempFileName = new Date().getTime() + (Math.floor(Math.random() * 1000) + '.jpg'); + + response.writeHead(200, { + 'Content-Type': 'image/jpeg', + 'Content-Disposition': 'inline; filename=' + tempFileName + }); + + wkhtmltoimage.setCommand(api.config.tcConfig.generateSourceCodeImage.wkhtmltoimageCommandPath); + wkhtmltoimage.generate(highlightResult, api.config.tcConfig.generateSourceCodeImage.wkhtmlToImageOptions, function (code, signal) { + if (code !== null && code === 0) { + // all success + cb(); + } else { + cb(new BadRequestError("Failed to generate the image, the return code is " + code)); + } + + }).pipe(response); + } + ], function (err) { + if (err) { + helper.handleError(api, connection, err); + next(connection, true); + } else { + next(connection, false); //false = response has been set + } + }); +}; + +/** + * The API for converted source code to image. + */ +exports.convertSourceCodeToImage = { + name: "convertSourceCodeToImage", + description: "Convert source code to image", + inputs: { + required: ['code', 'lang'], + optional: ['style'] + }, + blockedConnectionTypes: [], + cacheEnabled: false, + outputExample: {}, + version: 'v2', + run: function (api, connection, next) { + api.log("Execute convertSourceCodeToImage#run", 'debug'); + convertSourceCodeToImage(api, connection, next); + } +}; \ No newline at end of file diff --git a/apiary.apib b/apiary.apib index 8f8f4aeea..235aef0c5 100644 --- a/apiary.apib +++ b/apiary.apib @@ -14635,3 +14635,65 @@ Managing Round Question Answers APIs "description":"Servers are up but overloaded. Try again later." } + +# Group Source Code Image Generation APIs +Source Code Image Generation APIs + +## Source Code Image Generation API [/src2image] + +### Source Code Image Generation API [POST] + ++ Parameters + + code (required, string) ... the code value to convert + + lang (required, string) ... the code's language name, it should be one of ["1c","actionscript","apache","applescript","xml","asciidoc","autohotkey","avrasm","axapta","bash","brainfuck","capnproto","clojure","cmake","coffeescript","cpp","cs","css","d","markdown","dart","delphi","diff","django","dos","dust","elixir","ruby","erb","erlang-repl","erlang","fix","fsharp","gcode","gherkin","glsl","go","gradle","groovy","haml","handlebars","haskell","haxe","http","ini","java","javascript","json","lasso","less","lisp","livecodeserver","livescript","lua","makefile","mathematica","matlab","mel","mizar","monkey","nginx","nimrod","nix","nsis","objectivec","ocaml","oxygene","parser3","perl","php","powershell","processing","profile","protobuf","puppet","python","q","r","rib","rsl","ruleslanguage","rust","scala","scheme","scilab","scss","smalltalk","sql","stylus","swift","tcl","tex","thrift","twig","typescript","vala","vbnet","vbscript","vbscript-html","vhdl","vim","x86asm","xl"] + + style (optional, string) ... the code's format style, it should be one of ['arta', 'ascetic', 'atelier-dune.dark', 'atelier-dune.light', 'atelier-forest.dark', 'atelier-forest.light', 'atelier-heath.dark', 'atelier-heath.light', 'atelier-lakeside.dark', 'atelier-lakeside.light', 'atelier-seaside.dark', 'atelier-seaside.light', 'brown_paper', 'codepen-embed', 'color-brewer', 'dark', 'default', 'docco', 'far', 'foundation', 'github', 'googlecode', 'hybrid', 'idea', 'ir_black', 'kimbie.dark', 'kimbie.light', 'magula', 'mono-blue', 'monokai', 'monokai_sublime', 'obsidian', 'paraiso.dark', 'paraiso.light', 'pojoaque', 'railscasts', 'rainbow', 'school_book', 'solarized_dark', 'solarized_light', 'sunburst', 'tomorrow-night-blue', 'tomorrow-night-bright', 'tomorrow-night-eighties', 'tomorrow-night', 'tomorrow', 'vs', 'xcode', 'zenburn'] + ++ Response 200 (image/jpeg) + + { + // it contains the generation jpeg in response. + } + ++ Response 200 (application/json) + + { + "error": "Error: code is a required parameter for this action" + } + ++ Response 200 (application/json) + + { + "error": "Error: lang is a required parameter for this action" + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"The language name is invalid." + } + ++ Response 400 (application/json) + + { + "name":"Bad Request", + "value":"400", + "description":"The style name is invalid." + } + ++ Response 500 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Unknown server error. Please contact support." + } + ++ Response 503 (application/json) + + { + "name":"Service Unavailable", + "value":"503", + "description":"Servers are up but overloaded. Try again later." + } diff --git a/config/tc-config.js b/config/tc-config.js index a7bca914d..148212fa0 100644 --- a/config/tc-config.js +++ b/config/tc-config.js @@ -2,7 +2,7 @@ * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. * * @author vangavroche, Ghost_141, kurtrips, Sky_, isv, bugbuka, flytoj2ee, TCSASSEMBLER - * @version 1.27 + * @version 1.28 * changes in 1.1: * - add defaultCacheLifetime parameter * changes in 1.2: @@ -64,6 +64,8 @@ * - Add studioReview object for get studio review opportunities api. * Changes in 1.27: * Add userActivationResendLimit and userActivationCacheLifeTime for user activation email api. + * Changes in 1.28: + * Add source code image generation configuration. */ "use strict"; @@ -224,6 +226,16 @@ var config = { studioReview: { specTerms: 'http://studio.topcoder.com/?module=SpecViewReviewTerms&ct=', reviewTerms: 'http://studio.topcoder.com/?module=ViewReviewTerms&ct=' + }, + + generateSourceCodeImage: { + wkhtmltoimageCommandPath: process.env.WKHTMLTOIMAGE_COMMAND_PATH || 'wkhtmltoimage', + styleLink: process.env.HIGHLIGHT_STYLE_LINK || 'http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.3/styles/%OVERRIDE_STYLE_NAME%.min.css', + wkhtmlToImageOptions: { + Format: 'jpg', + Quality: 94, + width: process.env.WKHTMLTOIMAGE_IMAGE_WIDTH || 1024 + } } }; module.exports.tcConfig = config; diff --git a/deploy/ci.sh b/deploy/ci.sh index ef54732bf..a0143cf31 100644 --- a/deploy/ci.sh +++ b/deploy/ci.sh @@ -3,14 +3,18 @@ # # Copyright (C) 2013-2014 TopCoder Inc., All Rights Reserved. # -# Version: 1.1 -# Author: vangavroche, delemach, isv +# Version: 1.2 +# Author: vangavroche, delemach, isv, TCASSEMBLER # # changes in 1.1: # - added RESET_PASSWORD_TOKEN_CACHE_EXPIRY environment variable # - added RESET_PASSWORD_TOKEN_EMAIL_SUBJECT environment variable # - added REDIS_HOST environment variable # - added REDIS_PORT environment variable +# changes in 1.2 +# - added WKHTMLTOIMAGE_COMMAND_PATH environment variable +# - added WKHTMLTOIMAGE_IMAGE_WIDTH environment variable +# - added HIGHLIGHT_STYLE_LINK environment variable # export CACHE_EXPIRY=-1 @@ -86,3 +90,7 @@ export REDIS_PORT=6379 export DEVELOP_SUBMISSION_MAX_SIZE=6144 export WATERMARK_FILE_PATH=test/test_files/design_image_file_generator/studio_logo_watermark.png + +export WKHTMLTOIMAGE_COMMAND_PATH=/home/ubuntu/tmp/wkhtmltox-0.12.1/static-build/posix-local/wkhtmltox-0.12.1/bin/wkhtmltoimage +export WKHTMLTOIMAGE_IMAGE_WIDTH=1024 +export HIGHLIGHT_STYLE_LINK=http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.3/styles/%OVERRIDE_STYLE_NAME%.min.css \ No newline at end of file diff --git a/deploy/development.bat b/deploy/development.bat index df4269e6c..93f5215ea 100644 --- a/deploy/development.bat +++ b/deploy/development.bat @@ -2,14 +2,18 @@ REM REM Copyright (C) 2014 TopCoder Inc., All Rights Reserved. REM -REM Version: 1.1 -REM Author: TrePe, isv +REM Version: 1.2 +REM Author: TrePe, isv, TCASSEMBLER REM REM Changes in 1.1 REM - added RESET_PASSWORD_TOKEN_CACHE_EXPIRY environment variable REM - added RESET_PASSWORD_TOKEN_EMAIL_SUBJECT environment variable REM - added REDIS_HOST environment variable REM - added REDIS_PORT environment variable +REM Changes in 1.2 +REM - added WKHTMLTOIMAGE_COMMAND_PATH environment variable +REM - added WKHTMLTOIMAGE_IMAGE_WIDTH environment variable +REM - added HIGHLIGHT_STYLE_LINK environment variable REM tests rely on caching being off. But set this to a real value (or remove) while coding. @@ -83,3 +87,7 @@ rem set REDIS_HOST=localhost rem set REDIS_PORT=6379 set WATERMARK_FILE_PATH=test/test_files/design_image_file_generator/studio_logo_watermark.png + +set WKHTMLTOIMAGE_COMMAND_PATH=/home/ubuntu/tmp/wkhtmltox-0.12.1/static-build/posix-local/wkhtmltox-0.12.1/bin/wkhtmltoimage +set WKHTMLTOIMAGE_IMAGE_WIDTH=1024 +set HIGHLIGHT_STYLE_LINK=http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.3/styles/%OVERRIDE_STYLE_NAME%.min.css \ No newline at end of file diff --git a/deploy/development.sh b/deploy/development.sh index 7abfbfd28..b31464df6 100755 --- a/deploy/development.sh +++ b/deploy/development.sh @@ -3,8 +3,8 @@ # # Copyright (C) 2013-2014 TopCoder Inc., All Rights Reserved. # -# Version: 1.2 -# Author: vangavroche, isv +# Version: 1.3 +# Author: vangavroche, isv, TCASSEMBLER # changes in 1.1: # - add JIRA_USERNAME and JIRA_PASSWORD # changes in 1.2: @@ -12,6 +12,10 @@ # - added RESET_PASSWORD_TOKEN_EMAIL_SUBJECT environment variable # - added REDIS_HOST environment variable # - added REDIS_PORT environment variable +# changes in 1.3 +# - added WKHTMLTOIMAGE_COMMAND_PATH environment variable +# - added WKHTMLTOIMAGE_IMAGE_WIDTH environment variable +# - added HIGHLIGHT_STYLE_LINK environment variable # # tests rely on caching being off. But set this to a real value (or remove) while coding. @@ -88,3 +92,7 @@ export REDIS_PORT=6379 export DEVELOP_SUBMISSION_MAX_SIZE=6144 export WATERMARK_FILE_PATH=test/test_files/design_image_file_generator/studio_logo_watermark.png + +export WKHTMLTOIMAGE_COMMAND_PATH=/home/ubuntu/tmp/wkhtmltox-0.12.1/static-build/posix-local/wkhtmltox-0.12.1/bin/wkhtmltoimage +export WKHTMLTOIMAGE_IMAGE_WIDTH=1024 +export HIGHLIGHT_STYLE_LINK=http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.3/styles/%OVERRIDE_STYLE_NAME%.min.css diff --git a/package.json b/package.json index 9113ba302..91b1bf86d 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,9 @@ "archiver": "~0.6.1", "redis": "0.10.x", "temp": "0.7.0", - "ssha": "*" + "ssha": "*", + "highlight.js": ">= 8.3.0", + "wkhtmltoimage": ">= 0.1.3" }, "devDependencies": { "supertest": "0.8.x", diff --git a/routes.js b/routes.js index dcfb6a383..24fd7090f 100755 --- a/routes.js +++ b/routes.js @@ -1,9 +1,9 @@ /* * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. * - * @version 1.61 + * @version 1.62 * @author vangavroche, Sky_, muzehyun, kurtrips, Ghost_141, ecnu_haozi, hesibo, LazyChild, isv, flytoj2ee, - * @author panoptimum, bugbuka, Easyhard + * @author panoptimum, bugbuka, Easyhard, TCASSEMBLER * * Changes in 1.1: * - add routes for search challenges @@ -142,6 +142,8 @@ * - Add route for get user identity api. * Changes in 1.61: * - Added routes for modifying/deleting round question answers. + * Changes in 1.62: + * - Added route for src2image api. */ /*jslint node:true, nomen: true */ "use strict"; @@ -344,6 +346,7 @@ exports.routes = { { path: "/:apiVersion/software/reviewers/:contestType", action: "getChallengeReviewers" }, { path: "/:apiVersion/design/statistics/tops/:challengeType", action: "getStudioTops" }, { path: "/:apiVersion/data/challengetypes", action: "algorithmsChallengeTypes" } + ].concat(testMethods.get), post: [ { path: "/:apiVersion/users/resetPassword/:handle", action: "resetPassword" }, @@ -381,7 +384,8 @@ exports.routes = { { path: "/:apiVersion/data/srm/rounds/:questionId/question", action: "modifyRoundQuestion"}, { path: "/:apiVersion/data/srm/rounds/:roundId/components", action: "setRoundComponents"}, { path: "/:apiVersion/data/srm/rounds/:roundId/terms", action: "setRoundTerms"}, - { path: "/:apiVersion/data/srm/rounds", action: "createSRMContestRound" } + { path: "/:apiVersion/data/srm/rounds", action: "createSRMContestRound" }, + { path: "/:apiVersion/src2image", action: "convertSourceCodeToImage" } ], put: [ diff --git a/test/test.sourceCodeImage.js b/test/test.sourceCodeImage.js new file mode 100644 index 000000000..7aa439873 --- /dev/null +++ b/test/test.sourceCodeImage.js @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2014 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSASSEMBLER + * + * The test cases for sourceCodeImage.js. + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint node: true, stupid: true, unparam: true */ + +/** + * Module dependencies. + */ +var _ = require('underscore'), + async = require('async'), + request = require('supertest'), + chai = require('chai'), + fs = require('fs'); + +var assert = chai.assert; + +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080', + QUERY_URL = '/v2/src2image', + BASE_URL = __dirname + "/test_files/sourceCodeImage/"; + +/** + * Create request and return it + * @param {Object} data - the query data + * @return {Object} request + */ +function createRequest(data) { + return request(API_ENDPOINT) + .post(QUERY_URL) + .set('Content-Type', 'application/json') + .set("Accept", "application/json") + .send(data); +} + +/** + * Helper method for validating + * @param {Object} data - the query data + * @param {String} filePath - the file path + * @param {Function} done - the callback function + */ +function validateResult(data, filePath, done) { + data.code = fs.readFileSync(BASE_URL + filePath).toString(); + + createRequest(data).expect(200).end(function (err, res) { + if (err) { + done(err); + return; + } + assert.equal(res.headers["content-type"], 'image/jpeg', 'The content type should be image/jpeg.'); + done(); + }); +} + +/** + * Assert error request. + * + * @param {Object} data - the query data + * @param {Number} statusCode - the expected status code + * @param {String} errorDetail - the error detail. + * @param {Function} done the callback function + */ +function assertError(data, statusCode, errorDetail, done) { + createRequest(data).expect(statusCode).end(function (err, res) { + if (err) { + done(err); + return; + } + if (statusCode === 200) { + assert.equal(res.body.error, errorDetail, "Invalid error detail"); + } else { + assert.equal(res.body.error.details, errorDetail, "Invalid error detail"); + } + done(); + }); +} + +describe('Convert source code to image APIs', function () { + this.timeout(120000); // Wait 2 minutes, convert large code value might be slow. + + describe('Convert source code to image API', function () { + var validateData = {"code": "", "lang" : "java"}; + + it("Error: code is a required parameter for this action", function (done) { + validateData = {"code": "", "lang" : "java"}; + assertError(validateData, 200, "Error: code is a required parameter for this action", done); + }); + + it("Error: lang is a required parameter for this action", function (done) { + validateData = {"code": "for(var i=0;i<10;i++)alert(i);", "lang" : ""}; + assertError(validateData, 200, "Error: lang is a required parameter for this action", done); + }); + + it("The language name is invalid.", function (done) { + validateData = {"code": "for(var i=0;i<10;i++)alert(i);", "lang" : "invalid"}; + assertError(validateData, 400, "The language name is invalid.", done); + }); + + it("The style name is invalid.", function (done) { + validateData = {"code": "for(var i=0;i<10;i++)alert(i);", "lang" : "java", "style": "invalid"}; + assertError(validateData, 400, "The style name is invalid.", done); + }); + + it("Valid request for java.", function (done) { + validateData = {"code": "", "lang" : "java"}; + validateResult(validateData, 'java.txt', done); + }); + + it("Valid request for c++.", function (done) { + validateData = {"code": "", "lang" : "c"}; + validateResult(validateData, 'c++.txt', done); + }); + + it("Valid request for c#.", function (done) { + validateData = {"code": "", "lang" : "cs"}; + validateResult(validateData, 'cs.txt', done); + }); + + it("Valid request for python.", function (done) { + validateData = {"code": "", "lang" : "python"}; + validateResult(validateData, 'python.txt', done); + }); + + it("Valid request for vb.net.", function (done) { + validateData = {"code": "", "lang" : "vbnet"}; + validateResult(validateData, 'vbnet.txt', done); + }); + + it("Valid request for java 10k.", function (done) { + validateData = {"code": "", "lang" : "java"}; + validateResult(validateData, 'java-10k.txt', done); + }); + + it("Valid request for java 30k.", function (done) { + validateData = {"code": "", "lang" : "java"}; + validateResult(validateData, 'java-30k.txt', done); + }); + + it("Valid request for java 60k.", function (done) { + validateData = {"code": "", "lang" : "java"}; + validateResult(validateData, 'java-60k.txt', done); + }); + + it("Valid request for java 100k.", function (done) { + validateData = {"code": "", "lang" : "java"}; + validateResult(validateData, 'java-100k.txt', done); + }); + }); + +}); \ No newline at end of file diff --git a/test/test_files/sourceCodeImage/c++.txt b/test/test_files/sourceCodeImage/c++.txt new file mode 100644 index 000000000..6de0cd672 --- /dev/null +++ b/test/test_files/sourceCodeImage/c++.txt @@ -0,0 +1,66 @@ +#line 2 "PeriodicJumping.cpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REP(i,n) for (int i=0; i<(int)(n); ++i) +#define FOR(i,a,b) for (int i=(a); i<(int)(b); ++i) +#define FORE(i,n) for (__typeof((n).begin()) i=(n).begin(); i!=(n).end(); ++i) +#define ALL(c) (c).begin(), (c).end() +#define pb push_back +#define mp make_pair +#define fi first +#define se second + +using namespace std; + +typedef unsigned int uint; +typedef long long ll; +typedef unsigned long long ull; +typedef vector vi; +typedef vector vs; + +const int mod = 1000000007; + +class PeriodicJumping { +public: + int minimalTime(int x, vector len) { + x = abs(x); + if (x == 0) return 0; + int n = len.size(); + auto mx = max_element(len.begin(), len.end()); + int ret = INT_MAX; + if (x < *mx) { + ret = n + (mx - len.begin()) + 1; + } else { + long long sum = accumulate(len.begin(), len.end(), 0); + int time = (x / sum) * n; + long long rest = x % sum; + for (int i=0; i