const qB = {
  randomProduct: function({
                            minComponents,
                            amount,
                            single,
                            connectedByTags,
                            minSimilarTags,
                            doNotTrim
                          }, log) {
    let filtered = Object.values(dP.products);


    filtered = filtered
      .filter((p)=>p.getComponents().length >= minComponents);

    log.push(`Have >= ${minComponents} components: `+filtered.length);

    if(single){
      amount = 1;
    }

    let minAmount, maxAmount;

    if(typeof amount !== 'number'){
      minAmount = amount.from;
      maxAmount = amount.to;
    }else{
      minAmount = maxAmount = amount;
    }




    let similarClusters = [];
    if(connectedByTags){
      let tagsHash = {};
      filtered.forEach(p => {
        p.getTags().forEach(({id})=>
          (tagsHash[id] || (tagsHash[id] = [])).push(p)
        );
      });

      let similarTags = {};
      filtered.forEach(p => {
        const tags = p.getTags(),
              tagsStr = tags.map(({id})=>id).sort().join(',');
        if(tagsStr in similarTags)
          return;

        let similar = filtered
            .map(subP =>
              ({
                similar: subP.getTags().filter(subT => tags.indexOf(subT)>-1).length,
                p: subP
              })
            )
            .filter(i=>i.similar>0)
            .sort((a, b)=>b.similar - a.similar);

        if(similar.length){
          let maxCount = similar[ 0 ].similar;
          let minCount = minSimilarTags || Math.round( maxCount - maxCount / 4 );

          similarTags[ tagsStr ] = similar
            .filter( s => s.similar >= minCount )
            .map(s=>s.p)
        }

      });

      similarClusters = Object.keys(similarTags)
        .map(k=>({k, v:similarTags[k]}))
        .filter( a => a.v.length >= minAmount);


      log.push(`Clusters that have >= ${minSimilarTags} similar tags: `+Object.values(similarClusters).length);

      let randed = rand(similarClusters);

      log.push(`Used cluster tags: ${randed.k.split(',').map(id=>dP.tagsHash[id].name).join(', ')}`);

      filtered = randed.v;
    }


    let result;
    if(single){
      result = rand(filtered);
      log.push('Matched product: '+result.title);

    }else{
      if(filtered.length < minAmount){
        log.push(`Not enough components meeting criteria`);
        return false;
      }

      result = shuffle(filtered).slice(0, doNotTrim?filtered.length:rand(minAmount, maxAmount));
      log.push('Base products:');
      result.forEach(p => log.push(`  > ${p.title}`))
      log.push('');
    }

    return result;
  },

  subdivideComponents: function(from, sub) {
    const hash = {};
    from.forEach(c=>hash[normalizeText(c.name)] = c);
    sub.forEach(c=>delete hash[normalizeText(c.name)]);
    return Object.values(hash);

  },

  getUniqComponents: function(products) {
    const hash = {};
    products.forEach(
      p=>
        p
          .getComponents()
          .forEach(c=>
            hash[normalizeText(c.name)] = c
          )

    );
    return Object.values(hash);
  },

  getNotUniqComponentsWithProducts: function(products, minSharedProductCount) {
    const hash = {};
    products.forEach(
      p=>
        p
          .getComponents()
          .forEach(c=> {
            const name = normalizeText( c.name );
            (hash[ name ] || (hash[name] = [])).push({c, p});
          })

    );
    return Object
      .values(hash)
      .filter(i => i.length >= minSharedProductCount)
      .map(i=>({component: i[0].c, products: i.map(({p})=>p)}));
  },

  getComponentsNotInCorrect: function(income) {
    return shuffle(
      qB.subdivideComponents(
        qB.getUniqComponents(income.wrong),
        income.correct.getComponents()
      )
    );
  },

  prebuild: {
    similarTaggedProducts: {
      // minimal components in product
      questionCmpAmount: 4,

      // amount of matched products
      products: {from: 4, to: 8},

      // product similarity (minimal matched tags count)
      minSimilarTags: 2,

      fn(log){
        let products = qB.randomProduct({
          minComponents: this.questionCmpAmount,
          amount: this.products,
          connectedByTags: true,
          minSimilarTags: this.minSimilarTags
        }, log);

        for( let i = 0, _i = products.length; i < _i; i++ ){
          const product = products[ i ],
            cmps = product
              .getComponents()
              .filter(cmp => products.filter(p=>p.containsComponent(cmp)).length === 1)

          if(cmps.length>=this.questionCmpAmount){

            if(this.doNotLogUnique){
              log.push( `Product ${lapk( product.title )} have ${cmps.length} uniq components:` );
            }else{
              log.push( `Product ${lapk( product.title )} have >= ${this.questionCmpAmount} uniq components (${cmps.length}):` );
            }

            const shuffled = shuffle(cmps.slice()),
              used = shuffled.slice(0, this.questionCmpAmount);

            cmps.forEach(c=>log.push(`${!this.doNotLogUnique && used.indexOf(c)>-1?'+':' '} > ${c.name}`));

            let wrong = products.slice();
            wrong.splice(i,1);

            return {
              correct: product,
              wrong: wrong,
              uniq: used,
              allUniq: shuffled
            }
          }
        }
        return false;
      }
    }
  }
};