const sources = [
  "js/releasable-observer.js",
  "js/pcg-base.js",
  "js/pcg-dom-util.js",

  "js/helpers/rand.js",

  "js/model/store.js",
  "js/model/data.js",

  "js/model/provider.js",
  "js/model/slice/productTable.js",

  "js/model/tag.js",
  "js/model/product.js",

  "js/helpers/textFilter.js",
  "js/helpers/answer.js",

  "js/controller/quizBits/main.js",

  "js/controller/quizGenerator.js",
  "js/controller/quizBits/checkbox.js",
  "js/controller/quizBits/checkboxPhoto.js",
  "js/controller/quizBits/radio.js",
  "js/controller/quizBits/radioPhoto.js",
  "js/controller/quizBits/seal.js"
];
const fs = require('fs'),
  path = require('path');
var js = sources.map(a=>path.join(__dirname,'../../public',a)).map(n=>fs.readFileSync(n)+'').join('\n\n');


let data = require("../../db.js");
const body = 'const window = {}, localStorage = {getItem:()=>"{}"};'+js+'; return {shuffle, quizGenerator, standardGenerator, quizTypes, probabilityRand, initDataProvider, seeded: Math.random.seeded, rand, dP}';

var ctxCtor = new Function('',body);


var ctx;
var initCtx = function() {
  let ctxready = ctx && ctx.ready,
    ctxr = ctx && ctx._r;

  ctx = ctxCtor();
  ctx._r = ctxr;
  ctx.ready = ctxready || new Promise((r,j)=>{
    ctx._r = r;
  });
  ctx.setData = function(dataSet) {
    ctx.initDataProvider( dataSet );
    countStdQ = Object.values(dataSet.standardQuestions).length;
    //console.log(ctx.dP)
    data = dataSet

  };
};
const normalizeText = function(text) {
  return (text+'').trim().toLowerCase().replace(/[^а-я]/g,'').replace(/[аоуеэюёиыя]+/g,'о')
};
initCtx();
let standardQuestions;
let subcats = {};
data.after = function(inComeData, skipInit){
  if( inComeData ){
    for(let i in inComeData.products){
      let cmp = inComeData.products[i];
      subcats[ normalizeText( cmp.title ) ] = cmp.subcat;
    }
    ctx.setData(theData=inComeData);
    standardQuestions = inComeData.standardQuestions;
    firstRequest(true);
    //ctx._r();

  }
};



const csv = require('csv-parser');

const readCSV = async function(scope) {
  const {fileName, types, keys, index} = scope,
    postProcess = scope.postProcess || (a => a);

  const trash = Symbol('Trash')
  const hash = {};
  return await new Promise(function(resolve, reject) {
    let id = 1;
    const inc = ()=>id++;

    fs.createReadStream(fileName)
      .pipe(csv({
        mapHeaders: ({header}) => keys[header] || trash,
        mapValues: ({header, value}) => header in types ? types[header](value) : null
      }))
      .on('data', (obj) => {
        if(postProcess(obj, inc)!==false)
          hash[obj[index]] = obj;
      })
      .on('end', () => {
        resolve(hash);
        console.log('CSV parsed: '+ fileName)
      });
  });
};
const Consts = {
  CATEGORY: {
    PRODUCT: 1,
    INFO: 2
  }
};
    let BACKEND = process.env.KUS_GENERATOR_BACKEND;
if(!BACKEND)
  throw new Error('No backend specified');
let urlJoin = function(){
  let uri = [].slice.call(arguments).join('/');
  return uri.substr(0,9)+uri.substr(9).replace(/\/+/g, '/');
};
let afterLoaded = async function(infoHash) {
  ctx._r();
  return;// everything imported
  let categoryID = 51;
  console.log('Importing category:', categoryID);
  let hashSlice = {};
  for(let i in infoHash){
    if(infoHash[i].category_id === categoryID){
      hashSlice[i] = infoHash[i];
    }
  }

  let normalizedTitleHash = {};

  for(let i in hashSlice){
    //normalizedTitleHash[normalizeText(hashSlice[i].title)] = hashSlice[i];
    for( let j = 0, _j = hashSlice[ i ].items.length; j < _j; j++ ){
      const item = hashSlice[ i ].items[ j ];
      normalizedTitleHash[normalizeText(item.name || item.text)] = hashSlice[i];
    }
  }

  //let moreElement ={c: 'top-35-march-c.csv', q: 'top-35-march-q.csv', a: 'top-35-march-a.csv'};
  let moreElement = {c: 'top-35-april-c.csv', q: 'top-35-april-q.csv', a: 'top-35-april-a.csv'};

  const base = process.env.KUS_DATA_DIR || './data';

  let mapIDHash = {};
  var request = require('request');


  if( moreElement.c ){
    let cards = await readCSV({
      fileName: path.join(base,moreElement.c),
      keys: {
        'номер карточки': 'id',
        'Текст карточки': 'description',
        'Картинка': 'image',
        'Название': 'title',
        'категория': 'category_id'
      },
      types: {
        id: Number,
        title: String,
        description: String,
        category_id: String,
        image: String
      },
      postProcess: ( a, inc ) => {
        if( a.description.trim() === '' ) return false;
        let norm = normalizeText(a.description);//(a.title);
        if(norm in normalizedTitleHash){
          mapIDHash[a.id] = normalizedTitleHash[norm].id;
        }
        a.id = mapIDHash[a.id];

        a.use = ( a.use + '' )[ 0 ].toLowerCase() in { 'д': 1, '1': 1, 'y': '1', 'c': '1' };
        a.type = 2;
        a.category_id = moreElement.c[0]|0;
        a.items = [{
          description: a.description,
          image: a.image
        }];
        //debugger


      },
      index: 'id'
    });

    // SAVE!!!

/*
    for( let i in cards ){
      const card = cards[ i ];


      let saved = await new Promise((r,j)=>{
        var options = {
          'method': 'POST',
          'url': urlJoin(BACKEND,'/api/admin/card/'),
          'headers': {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({

            //"image": item.image,
            "category_id": categoryID,
            "name": card.title,
            "type": Consts.CATEGORY.INFO,
            "hidden": 0,
            "text": 'empty text',
            is_test: 1,
            "tags": [],
            "components": []
          })
        };
        request(options, function (error, response) {
          if (error) throw new Error(error);
          r(JSON.parse(response.body));
        });
      });
      debugger;

      let sub = await new Promise((r,j)=>{
        var options = {
          'method': 'POST',
          'url': urlJoin(BACKEND,'/api/admin/carditem/'),
          'headers': {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            card_id: saved.id,
            text: card.description,
            image: null
          })
        };
        request(options, function (error, response) {
          if (error) throw new Error(error);
          r(JSON.parse(response.body));
        });
      });

      debugger;

    }*/
  }
  /*debugger
  return;*/
  if( moreElement.q ){
    const Q = await readCSV( {
      fileName: path.join( base, moreElement.q ),
      keys: {
        'номер вопроса': 'qID',
        'карточка': 'cardInfoID',
        'Заголовок': 'title',
        'Тип вопроса': 'image',
        'множественный выбор': 'multiple',
        'Тип ответа': 'answer_type',
        '\\': 'category',
        'категория': 'category'
      },
      types: {
        qID: Number,
        cardInfoID: Number,
        title: String,
        image: String,
        multiple: String,
        category: Number,
        answer_type: String
      },
      postProcess: ( a ) => {
        if( a.title.trim() === '' ) return false;
        a.cardInfoID = mapIDHash[a.cardInfoID];

        a.category_id = categoryID;
        a.answers = [];
        a.multiple = ( a.multiple + '' )[ 0 ].toLowerCase() in { 'д': 1, '1': 1, 'y': '1', 'c': '1' };
        //a.image = a.image.indexOf('с ')>-1? hashSlice[a.cardInfoID].image :null;
        a.image=null;
      },
      index: 'qID'
    } );

let inc = 1;
    const A = await readCSV( {
      fileName: path.join( base, moreElement.a ),
      keys: {
        'номер вопроса': 'qID',
        'ответ': 'title',
        'правильный': 'correct',
      },
      types: {
        qID: Number,
        title: String,
        correct: String
      },
      postProcess: ( a, inc ) => {
        if( a.title.trim() === '' ) return false;

        a.id = inc++;
        a.correct = ( a.correct + '' )[ 0 ].toLowerCase() in { 'д': 1, '1': 1, 'y': '1', 'c': '1' };
        Q[ a.qID ].answers.push( a );
      },
      index: 'id'
    } );

    var request = require('request');

    let QS = Object.values(Q);
    for( let i = 0, _i = QS.length; i < _i; i++ ){
      const q = QS[ i ];

     // debugger
      // REAL SAVE
     // return;

      let saved = await new Promise((r,j)=>{
        var options = {
          'method': 'POST',
          'url': urlJoin(BACKEND,'/api/admin/question/'),
          'headers': {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({"card_info_id":q.cardInfoID,"card_id":q.cardInfoID,"text":q.title,"type":q.multiple?'checkbox':'radio'})

        };
        request(options, function (error, response) {
          if (error) throw new Error(error);
          r(JSON.parse(response.body));
        });
      });

      let questionID = saved.id;

      console.log(i,'/',_i,' Question added',questionID, q.title);
      for( let j = 0, _j = q.answers.length; j < _j; j++ ){
        const answer = q.answers[ j ];

        let answerResponse = await new Promise((r,j)=>{
          var options = {
            'method': 'POST',
            'url': urlJoin(BACKEND,'/api/admin/answer/'),
            'headers': {
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({"question_id":questionID,"text": answer.title,"correct":answer.correct?1:0})

              //{"card_info_id":q.cardInfoID,"text":q.title,"type":q.multiple?'checkbox':'radio'})

          };
          request(options, function (error, response) {
            if (error) throw new Error(error);
            r(JSON.parse(response.body));
          });
        });
        console.log('  Answer added',answerResponse.data.id, answer.title);
      }
      console.log('');
    }

   /* for( var qI in Q ){
      let id = ++maxQ;
      Q[ qI ].qID = id;
      Q[ qI ].answers.forEach( a => a.qID = id );
      data.standardQuestions[ id ] = Q[ qI ];
    }*/
  }


}






let theData;
let countStdQ;
let firstRequest;
module.exports = {
  '/api/database/get': {
    method: 'GET',
    summary: 'Return the database',
    options: {},
    fn: async function(){
      await ctx.ready;
      return theData;
    }
  },
  '/api/database/update': {
    method: 'GET',
    summary: 'Reload category, tags and questions from '+BACKEND,
    options: {},
    fn: firstRequest = async function() {

      var request = require('request');//function(o, fn){fn(false, {body:JSON.stringify(require('../../data/mocked.json'))})};//
      var options = {
        'method': 'GET',
        'url': urlJoin(BACKEND,'/api/admin/category/'),
        'headers': {}
      };
      request(options, function (error, response){
        if( error ) throw new Error( error );
        var s = data,
          l = JSON.parse( response.body );
        console.log('Request: category loaded');

        var options = {
          'method': 'GET',
          'url': urlJoin(BACKEND,'/api/admin/tag/'),
          'headers': {}
        };
        request( options, function( error, response ){
          if( error ) throw new Error( error );
          var tags = JSON.parse( response.body );
          console.log('Request: tag loaded');
          var options = {
            'method': 'GET',
            'url': urlJoin(BACKEND,'/api/admin/question/'),
            'headers': {}
          };
          request( options, function( error, response ){
            if( error ) throw new Error( error );
            var questions = JSON.parse( response.body );
            console.log('Request: question loaded');


            var qCardHash = {}, infoHash = {};
            questions.forEach(function(q) {
              (qCardHash[q.card_info_id] || (qCardHash[q.card_info_id]=[])).push(q);
            });

            var mimicri = {
              tags: tags,
              connections: [],
              products: {},
              components: {},
              standardQuestions: {},
              category: {}
            };
            var cmpID = 1;
            l.forEach( category => {
              mimicri.category[category.id] = {name: category.name, title: category.title, type: category.type, id: category.id};
              if( category.type === 1 && !category.hidden ){
                category.cards.forEach( card => {
                  card.components.forEach( cmp => {
                    mimicri.components[ cmpID++ ] = {
                      id: card.id,
                      iID: cmp.id,
                      name: cmp.name
                    };
                  } );

                  mimicri.products[ card.id ] = {
                    tags: card.tags.map( t => t.id ),

                    "category_id": card.category_id,
                    "id": card.id,
                    "title": card.name,
                    "description": card.text,
                    "subcat": subcats[ normalizeText( card.name ) ],
                    "use": card.is_test,
                    "image": card.image === null ? void 0 : card.image,
                    "type": card.type,
                    "items": card.components
                  };
                } )
              }else if( category.type === 2 && !category.hidden ){
                category.cards.forEach( card => {
                  infoHash[card.id] = card;
                  var base = {
                    category: card.category_id,
                    cardInfoID: card.id,
                    category_id: card.category_id
                  };
                  if(card.id in qCardHash){
                    qCardHash[card.id].forEach(q=>{
                      mimicri.standardQuestions[q.id] = Object.assign({
                        qID: q.id,
                        title: q.text,
                        multiple: q.type !== 'radio',
                        image: card.image,
                        answers: q.answers.map(a=>{
                          return {
                            "qID": q.id,
                            "title": a.text,
                            "correct": a.correct,
                            "id": a.id
                          };
                        })
                      }, base);

                    });
                   /* {
                      "id": 4,
                      "card_info_id": 480,
                      "text": "ДА?",
                      "image": null,
                      "type": "radio",
                      "created_at": "2020-03-04 20:32:05",
                      "updated_at": "2020-03-04 21:29:36",
                      "answers": [
                      {
                        "id": 99,
                        "question_id": 4,
                        "text": "Да",
                        "correct": true,
                        "created_at": "2020-03-04 20:32:44",
                        "updated_at": "2020-03-04 20:32:44"
                      },
                      {
                        "id": 100,
                        "question_id": 4,
                        "text": "Нет",
                        "correct": false,
                        "created_at": "2020-03-04 20:34:06",
                        "updated_at": "2020-03-04 20:34:06"
                      },
                      {
                        "id": 107,
                        "question_id": 4,
                        "text": "Мда",
                        "correct": true,
                        "created_at": "2020-03-04 21:29:03",
                        "updated_at": "2020-03-04 21:29:03"
                      },
                      {
                        "id": 108,
                        "question_id": 4,
                        "text": "Мнет",
                        "correct": false,
                        "created_at": "2020-03-04 21:29:05",
                        "updated_at": "2020-03-04 21:29:05"
                      },
                      {
                        "id": 113,
                        "question_id": 4,
                        "text": "TTT",
                        "correct": false,
                        "created_at": "2020-03-04 21:46:24",
                        "updated_at": "2020-03-04 21:46:24"
                      }
                    ],
                      "media": []
                    }*/
                  }
                } );

                /*
                category: 2
                qID: 2
                title: "С какого года на рынке компания "Избёнка"?"
                cardInfoID: 301
                multiple: false
                image: null
                answer_type: "текст"
                category_id: 2
                 */


              }
            } );
            //mimicri.standardQuestions = standardQuestions;
            //console.log(response.body);
            initCtx();
            theData = JSON.stringify( mimicri );
            ctx.setData( mimicri );

            afterLoaded(infoHash);

          });
        } );
      });
      return 'true';
    }

  },
  '/api/get/questions': {
    method: 'GET',
    summary: 'Get questions attached to standard card',
    options:{
      id: {required: true, description: 'Item ID', type: Number},
      category: {required: false, description: 'Item Category', type: Number},
      title: {required: false, description: 'Item Category', type: String}
    },
    fn: async function(args) {
      debugger
      ctx.probabilityRand
    }
  },
  '/api/generate/random': {
    method: 'GET',
    summary: 'Generate random quiz',
    options:{
      seed: {required: false, description: 'initialize random state. Make response determined', type: String},
      photo: {required: false, description: 'question with photo', type: Boolean},
      type: {required: false, description: '1 - products, 2 - standards', type: Number},
      nolog: {required: false, description: 'remove human readable log', type: Boolean},
      exclude: {required: false, description: 'remove human readable log', type: Array}
    },
    fn: async function(args) {
      await ctx.ready; const rand = ctx.rand, seeded = ctx.seeded;
      let seed = args.seed || Math.random().toString(36).substr(2);
      ctx.seeded.setStringSeed( seed );

      let option = ctx.probabilityRand([].concat.apply([],Object.values(ctx.quizTypes)))

      let multiple = option.multiple,//seeded() > 24 / ( 46 + 24 ),
          photo = option.photo, //seeded() > 0.7,
          result, attempts = 0;

      let noProducts = Object.keys(ctx.dP.products).length === 0
      while(!result){
        if( ( args.type !== void 0 ? args.type === 2 : seeded() > 0.5 ) || noProducts ){
          result = ctx.standardGenerator( () => rand( 1, countStdQ ) )
        }else{
          result = ctx.quizGenerator(
            multiple ? 'checkbox' : 'radio',
            args.photo !== void 0 ? args.photo : photo,
            (p)=>{

              return p.use||p.is_test}
          );
        }
        if(result && args.exclude){
          if(args.exclude.filter((a)=>a.categoryId === result.categoryId && a.productId === result.productId).length){
            attempts++;
            if(attempts < 20){
              result = false;
            }
          }
        }
      }

      console.log(`cat: ${result.categoryId}, prod: ${result.productId}, seed: ${seed}. ${result.question.substr(0,33)}`);

      if(args.nolog)
        delete result.log;

      return result;
    }
  },
  '/api/generate/random/sequence': {
    method: 'GET',
    summary: 'Generate list of random quizes',
    options:{
      count: {required: true, description: 'Count of generated questions', type: Number},
      seed: {required: false, description: 'initialize random state. Make response determined', type: String},
      photo: {required: false, description: 'question with photo', type: Boolean},
      type: {required: false, description: '1 - products, 2 - standards', type: Number},
      nolog: {required: false, description: 'remove human readable log', type: Boolean},
      stats: {required: false, description: 'aggregate stats and explain questions', type: Boolean},
      human: {required: false, description: 'aggregate stats and explain questions', type: Boolean},
      category: {required: false, description: 'comma separated categories', type: String},
      tags: {required: false, description: 'comma separated tags', type: Array},
    },
    fn: async function(args, req, res) {
      await ctx.ready; const rand = ctx.rand, seeded = ctx.seeded;
      //console.log('Generate sequence', args);

      let useCategory = false,
        categoryHash = {};

      if(args.category){
        useCategory = true;
        categoryHash = args.category
          .split(',')
          .map(a=>a.trim())
          .reduce((s,c)=>{s[c]=1; return s}, {});

      }
      let error = false;
      let seed = args.seed || Math.random().toString(36).substr(2);
      ctx.seeded.setStringSeed( seed );
      let used = {}, generated = 0;
      let last = {c:-1},
        list = [],
        globalMaxTries = args.count*500,
        globalTries = 0;
      if(args.count>100)args.count = 100;


      if(args.tags){
        let tags = args.tags.map(t=>ctx.dP.tagsHash[t]);
        let info = {};
        let isTest = {};
        let isTestFn = (p)=>{
          let c = p.category_id, id = p.id;
          return isTest[c] && isTest[c][id];
        };
        let productTagCount = 0,
          pCount = 0;

        tags.forEach(t=>{
          if(t.type_id === 7){
            let category = ctx.dP.category[t.category_id];
            if(category.type === 2){
              info[ category.id ] = true;
            }else{
              if(!(category.id in isTest))
                isTest[category.id] = {};

              Object.values(ctx.dP.products)
                .filter(p=>p.category_id === category.id)
                .forEach(c=>isTest[category.id][c.id] = true);
            }
          }else if(t.type_id === 13){
            productTagCount++;
            Object.values(ctx.dP.products)
              .filter(p=>p.tags && p.tags.indexOf(t.id)>-1)
              .forEach(p=>{
                if(!(p.category_id in isTest))
                  isTest[p.category_id] = {};
                pCount++;
                isTest[p.category_id][p.id] = true;
              });
          }
        });

        // СФОРМИРОВАЛИ ОЖИДАНИЮ

        let queues = {};
        Object.keys(info).forEach(k=>{
          k=parseInt(k,10);
          queues[k] = ctx.shuffle(Object.values( data.standardQuestions )
            .filter(q=>q.category_id === k)
            .map( ( q ) => q.qID ));
        });
        let groups = [];
        Object.keys(queues).forEach(k=>{
          groups.push({type: 2, id: k, count: queues[k].length});
        });
        for(let i = 0; i <productTagCount; i++){
          groups.push({type: 1, count: pCount});
        }
        let step = groups.slice();
        while( generated < args.count && globalTries < globalMaxTries ){
          let result;

          globalTries++;
          let take = ctx.rand(step);

          if(take.type === 2){
            let took = queues[take.id].pop();
            take.count--;
            if(!take.count){
              groups.splice(groups.indexOf(take),1);
              if(!groups.length)
                break;
            }
            result = ctx.standardGenerator( () => took );
          }else{
            let option = ctx.probabilityRand([].concat.apply([],Object.values(ctx.quizTypes)))
            let multiple = option.multiple,
              photo = option.photo;

            result = ctx.quizGenerator(
              multiple ? 'checkbox' : 'radio',
              args.photo !== void 0 ? args.photo : photo,
              isTestFn
            );
          }


          if(result && (!(result.categoryId + '.' + result.productId in used) || take.type === 2)){
            used[ result.categoryId + '.' + result.productId ] = true;
            list.push( result );
            generated++;
          }
          step = groups.length>1?groups.slice().filter(i=>i!== take): groups.slice();
        }

        // new generator;

      }else{

        while( generated < args.count && globalTries < globalMaxTries ){
          globalTries++;

          let option = ctx.probabilityRand( [].concat.apply( [], Object.values( ctx.quizTypes ) ) )

          let multiple = option.multiple,//seeded() > 24 / ( 46 + 24 ),
            photo = option.photo; //seeded() > 0.7

          let cur = {
            m: multiple,//seeded() > 24 / ( 46 + 24 ),
            p: photo,//seeded() > 0.7,
            c: 1
          };
          if( ( useCategory && !categoryHash[ 1 ] ) || seeded() > 0.3 ){
            //if( last.c === 1 && seeded() > 0.5 ){
            cur = {
              c: 2
            };
            let id, tries = 0;
            /*do{
              id = rand(1, countStdQ);
              tries++;
              if(tries === 100){
                break;
              }
            }while('2.'+id in used);*/
            if( tries === 100 )
              continue;
            cur.id = id;
          }

          let tries = 0,
            result;
          do{
            if( cur.c === 1 ){
              result = ctx.quizGenerator( cur.m ? 'checkbox' : 'radio', cur.p );
            }else{
              let counter = 0, r = 2;

              if( useCategory ){

                let possible = Object.values( data.standardQuestions )
                  .map( ( q ) => ( { q, c: q.category_id, n: q.qID } ) )
                  .filter( q => q.c in categoryHash )
                  .filter( q => !( q.c + '.' + q.n in used ) )
                  .map( q => q.n );
                if( possible.length ){
                  r = rand( possible )
                }else{
                  error = 'Not enough questions';
                  break;
                }

              }else{
                do{
                  r = rand( 1, countStdQ );
                  let q = data.standardQuestions[ r ];
                  if( q && !( q.category_id + '.' + q.qID in used ) )
                    break;
                  counter++;
                }while( counter < 100 );
              }

              result = ctx.standardGenerator( () => r );


              if( !result ){
                tries = 100;
                break;
              }
            }
            tries++;
            if( tries === 100 )
              break;
          }while( result.categoryId + '.' + result.productId in used || ( ( cur.c > 1 && result.categoryId === last.c ) && !useCategory ) );

          if( error )
            break;
          if( tries !== 100 ){
            cur.c = result.categoryId;

            used[ result.categoryId + '.' + result.productId ] = true;
            list.push( result );
            if( args.nolog )
              delete result.log;
            last = cur;
            generated++;
            result.number = generated;
          }
        }
      }






      if(args.stats){
        res.header("Content-Type", "text/html; charset=utf-8");
        let stats = {};
        let statsImage = {};
        let statsCategory = {};
        let statsType = {};

        list.forEach(i=>{
          if(i.categoryId === void 0)
            debugger;
          let key = i.type+', '+(i.image?'image':'no image')+', '+i.categoryId;
          stats[key] = (stats[key]||0)+1;

          key = i.type+'';
          statsType[key] = (statsType[key]||0)+1;

          key = (i.image?'image':'no image');
          statsImage[key] = (statsImage[key]||0)+1;

          key = i.categoryId+'';
          statsCategory[key] = (statsCategory[key]||0)+1;


        });
        return '<HTML><head></head><body>' +
          `<h2>Generated: ${list.length}</h2>` +
          '<div>'+Object.keys(stats).map(k=>`<b>${k}</b>: <span>${stats[k]}</span>`).join('<br/>')+'</div>'+
          '<h2>Image</h2><div>'+Object.keys(statsImage).map(k=>`<b>${k}</b>: <span>${statsImage[k]}</span>`).join('<br/>')+'</div>'+
          '<h2>Category</h2><div>'+Object.keys(statsCategory).map(k=>`<b>${k}</b>: <span>${statsCategory[k]}</span>`).join('<br/>')+'</div>'+
          '<h2>Type</h2><div>'+Object.keys(statsType).map(k=>`<b>${k}</b>: <span>${statsType[k]}</span>`).join('<br/>')+'</div>'+
          '</body></HTML>'
      }
      if(args.human){
        res.header("Content-Type", "text/html; charset=utf-8");
        return '<HTML><head>'+`<style>
.hidden {
display: none
}
.how-block {
white-space: pre-wrap;
    padding: 0 16px;
    border-left: 4px solid #ff0060;
}
.seed {color: #999}
.title {font-size:26px}
*{font-family: Verdana;}
pre {font-family: monospace;}
.how {color: #ff0060;border-bottom: 1px dotted;font-size:14px;cursor: pointer}
</style><script>var toggle=function(a){a.parentNode.parentNode.querySelector('.how-block').classList.toggle('hidden')}</script>`+'</head><body><div class="seed">seed: '+seed+'</div> ' +
          list.map((result, i)=>
          `<div class="quest"><div class="title">${i+1}) ${result.question} <span class="how" onclick="toggle(this)">Как так получилось?</span></div> 
<pre>${result.image?'<img height="100px" src="'+TRANSFORMER.IMAGE(result.image)+'" style="float: left"/>':''}
${ result.answers.map(a=>
            ( (result.type==='checkbox'?(a.correct?'[v]':'[ ]'):(a.correct?'(o)':'( )'))+ ' '+a.text)
          ).join('\n')}

</pre>
<pre class="how-block hidden">${result.log.join('\n')}</pre>
</div>
`
          ).join('<br/><br/>')+
          '</body></HTML>'


      }
      return list;
    }
  }
};

const TRANSFORMER = {
  IMAGE: function( image ){
    if(!image || image === 'null' || image === '0' || image === '' || image===null){
      return null;
    }else{

      image = image.trim();
      if(image.indexOf('http')===0){
        if( image.indexOf( 'https://api.new.local.vkusvill.testin.ru/storage ' ) === 0 ){
          image = image.replace( /\s/g, '' );
        }
        return image;
      }
      if( image.indexOf( '/upload/resize' ) === 0 ){
        image = 'https://vkusvill.ru' + image;
      }else{

        image = image ? 'https://api.new.local.vkusvill.testin.ru/storage' + image.trim() : void 0;
      }
    }
    return image;
  }
}