//-------------- cartelle.cpp ------------------------------
#include <stdlib.h>
#include <string.h>

#include "cartelle.h"
//--------------------------------------------------
const int MAX_RANGE = 11;// l'ultima riga va da 80 a 90, quindi usa 11 numeri
const int TOT_ROW = 3;    // righe della cartella
const int TOT_COLUMN = 9; // colonne della cartella
const int TOT_CART  = (TOT_ROW * TOT_COLUMN);
const int MAX_NUMB_ROW = 5; // numeri possibili per riga
const int MAX_NUMB_CART = (MAX_NUMB_ROW * TOT_ROW);

// se si desiderano avere ordinati i numeri sulla colonna
//#define ORDINATI
//--------------------------------------------------
class ten
{
  public:
    ten(tRange low, tRange hight);

    //  Preleva il primo valore di threeVal e fa salire gli altri.
    //  Se il nuovo primo valore è zero, ricarica dal buffer e se è
    //  ancora zero, cioè è finito il buffer richiama reload()
    tRange getNumber();

    // torna il numero di elementi totali disponibili
    int getTotNumber();

    //  riporta il contenuto di threeVal in buff, ricarica
    // threeVal con tre valori casuali togliendoli da buff e li ordina (se
    // è attiva ORDINATI).
    // Da richiamare prima della generazione della cartella
    void shuffle();

  private:
    // tre valori casuali estratti da buff
    tRange threeVal[TOT_ROW];
    tRange Buff[MAX_RANGE];

    // numero di elementi contenuti in Buff (esclusi quelli in threeVal)
    int Tot;
    tRange Low;
    tRange Hight;

    // ricarica buffer e richiama shuffle()
    void reload();
};
//--------------------------------------------------
//--------------------------------------------------
ten::ten(tRange low, tRange hight) : Low(low), Hight(hight)
{
  reload();
}
//--------------------------------------------------
void ten::reload()
{
  int j;
  for(Tot = 0, j = Low; j <= Hight; ++j, ++Tot)
    Buff[Tot] = j;
  memset(threeVal, 0, sizeof(threeVal));
  shuffle();
}
//--------------------------------------------------
int ten::getTotNumber()
{
  int i;
  for(i = 0; i < TOT_ROW; ++i)
    if(!threeVal[i])
      break;
  return i + Tot;
}
//--------------------------------------------------
tRange ten::getNumber()
{
  tRange ret = threeVal[0];
  int i;
  for(i=0; i < TOT_ROW-1; ++i)
    threeVal[i] = threeVal[i+1];
  threeVal[i]=0;
  if(!threeVal[0]) {
    shuffle();
    if(!threeVal[0])
      reload();
    }
  return ret;
}
//--------------------------------------------------
void ten::shuffle()
{
  int i;
  for(i=0; i < TOT_ROW; ++i)
    if(threeVal[i]) {
      Buff[Tot++] = threeVal[i];
      threeVal[i] = 0;
      }
    else
      break;
  for(i=0; i < TOT_ROW && Tot; ++i) {
    int pos = rand() % Tot;
    threeVal[i] = Buff[pos];
    --Tot;
    Buff[pos] = Buff[Tot];
#if 0
    Buff[Tot]=0; // può servire per un debug
#endif
    }
#ifdef ORDINATI
  // riordina i due/tre valori
  if(i ==2) {
    if(threeVal[0] > threeVal[1]) {
      tRange t = threeVal[0];
      threeVal[0] = threeVal[1];
      threeVal[1] = t;
      }
    }
  else if(i == 3) {
    for(int i = 1; i < 3; ++i) {
      tRange t = threeVal[i]; // elemento da inserire
      int j;
      for(j = i - 1 ; j >= 0 && t < threeVal[j]; --j)
        threeVal[j + 1]=threeVal[j];
      threeVal[j + 1]=t;
      }
    }
#endif
}
//--------------------------------------------------
//--------------------------------------------------
//--------------------------------------------------

class cartella
{
  public:
    cartella();
    ~cartella();

    // buff deve avere dimensione almeno TOT_CART
    void make(tRange *buff);

  private:
    class ten **Ten;
    int Tot;
    int PosTmp;
    int Row[TOT_ROW][MAX_NUMB_ROW];
    int Numb[TOT_COLUMN];
    int NumbTmp[TOT_COLUMN];

    // carica in numb i sei oggetti ten che hanno il numeri più alto di elementi
    void getTopSix(int *numb);

    // calcola se occupati i bit vicini a pos e ne ritorna il peso
    int best_of(int pos, int bit);

    // calcola quale bit dà il peso maggiore
    int bestOf(int bit0, int bit1);

    // esegue lo spostamento di un elemento da una riga all'altra per evitare
    // di avere più di tre numeri vicini nella direzione orizzontale
    void move(int rowBit_0, int rowBit_1, int *row_0, int *row_1);

    // crea la riga in modo casuale
    void makeRow(int *row);

    // verifica se ci sono più di tre elementi vicini ed in caso affermativo
    // scambia con le altre righe
    void checkIfTooNear();

    // estrae i numeri da Ten alle posizioni date da Row
    void extract(tRange *buff);
};
//-------------------------------------------------
cartella::cartella()
{
  Ten = new ten*[TOT_COLUMN];
  const tRange range[] = { 0, 9, 19, 29, 39, 49, 59, 69, 79, 90 };
  for(int i = 0; i < TOT_COLUMN; ++i)
    Ten[i] = new ten(range[i]+1,range[i+1]);
}
//--------------------------------------------------
cartella::~cartella()
{
  for(int i = 0; i < TOT_COLUMN; ++i)
    delete Ten[i];
  delete []Ten;
}
//--------------------------------------------------
void cartella::getTopSix(int *numb)
{
  int top[TOT_COLUMN];
  for(int i = 0; i < TOT_COLUMN; ++i)
    top[i] = Ten[i]->getTotNumber();
  for(int i = 0; i < TOT_COLUMN; ++i) {
    --top[numb[i]];
    numb[i] = i;
    }
  for(int i = 1; i < TOT_COLUMN; ++i) {
    int t = top[i]; // elemento da inserire
    tRange n = numb[i];
    int j;
    for(j = i - 1 ; j >= 0 && t > top[j]; --j) {
      top[j + 1] = top[j];
      numb[j + 1] = numb[j];
      }
    top[j + 1] = t;
    numb[j + 1] = n;
    }
  int j=0;
  for(int i = MAX_NUMB_CART - TOT_COLUMN - 1; i; --i) {
    if(!top[i])
      numb[i] = numb[j++];
    else
      break;
    }
}
//---------------------------------------------------
int cartella::best_of(int pos, int bit)
{
    int bestLow = 0;
    int bestHight = 0;
    int side = 0;
    if(pos > 0) {
      bestLow += (bit & (1 << (pos-1))) != 0;
      if(pos > 1)
        bestLow += (bit & (1 << (pos-2))) != 0;
      else if(bestLow)
        ++side;
      }
    else
      side = 2;
    if(pos < TOT_COLUMN-1) {
      bestHight += (bit & (1 << (pos+1))) != 0;
      if(pos < TOT_COLUMN-2)
        bestHight += (bit & (1 << (pos+2))) != 0;
      else if(bestHight)
        ++side;
      }
    else
      side = 2;
    if(2 == side)
      return (bestLow + bestHight) << 1;
    return bestLow + bestHight + side;
}
//---------------------------------------------------
int cartella::bestOf(int bit0, int bit1)
{
    int bit = bit0 ^ bit1;
    int bit_0 = bit0 & bit;
    int best = 0;
    int bestVal = 0;
    for(int i = 0; i < TOT_COLUMN; ++i) {
      if(bit_0 & (1 << i)) {    // solo quelli che possono essere trasferiti
        int result = best_of(i, bit0);
        if(result >= bestVal) {
          bestVal = result;
          best = i;
          }
        }
      }
    return best;
}
//---------------------------------------------------
void cartella::move(int rowBit_0, int rowBit_1, int *row_0, int *row_1)
{
    int best_0 = bestOf(rowBit_0, rowBit_1);
    int best_1 = bestOf(rowBit_1, rowBit_0);
    for(int i = 0; i < MAX_NUMB_ROW; ++i)
      if(row_0[i] == best_0) {
        best_0 = i;
        break;
        }
    for(int i = 0; i < MAX_NUMB_ROW; ++i)
      if(row_1[i] == best_1) {
        best_1 = i;
        break;
        }
    int t = row_0[best_0];
    row_0[best_0] = row_1[best_1];
    row_1[best_1] = t;
}
//-----------------------------------------------------
void cartella::makeRow(int* row)
{
  for(int j = 0; j < MAX_NUMB_ROW; ++j) {
    int pos;
    if(PosTmp >= 0) {
      pos = rand() % Tot;
      row[j] = Numb[pos];
      NumbTmp[PosTmp++] = Numb[pos];
      }
    else {
      for(;;) {
        pos = rand() % Tot;
        int k;
        for(k = 0; k < j; ++k)
          if(row[k] == Numb[pos])
            break;
        if(k == j)
          break;
        }
      }
    row[j] = Numb[pos];
    if(!--Tot) {
      memcpy(Numb, NumbTmp, TOT_COLUMN * sizeof(Numb[0]));
      getTopSix(Numb);
      Tot = MAX_NUMB_CART - TOT_COLUMN;
      PosTmp = -1;
      }
    else {
      Numb[pos] = Numb[Tot];
      Numb[Tot] = 0;
      }
    }
}
//-----------------------------------------------------
#define MASK_CONS 0x8000
#define MAX_REPEAT 10
void cartella::checkIfTooNear()
{
  int maxRepeat = 0;
  int last = -1;
  for(;;) {
    int rowBit[TOT_ROW];
    for(int i = 0; i <  TOT_ROW; ++i) {
      rowBit[i] = 0;
      for(int j = 0; j < MAX_NUMB_ROW; ++j) {
        int bit = Row[i][j];
        rowBit[i] |= 1 << bit;
        }
      int cons = 0;
      for(int j = 0; j < TOT_COLUMN-1; ++j) {
        if((rowBit[i] & (3 << j)) == (3 << j)) {
          if(++cons >= 3) {
            rowBit[i] |= MASK_CONS;
            break;
            }
          }
        else
          cons = 0;
        }
      }
    if(MAX_REPEAT <= maxRepeat++)
      break;
#define MOVE(a,b) move(rowBit[a], rowBit[b], Row[a], Row[b])
    if(rowBit[0] & MASK_CONS) {
      int t = 0 == last ? 2 : 1;
      MOVE(0, t);
      last = 0 == last ? 2 : 0;
      continue;
      }
    if(rowBit[1] & MASK_CONS) {
      int t = 1 == last ? 0 : 2;
      MOVE(1, t);
      last = 1 == last ? 0 : 1;
      continue;
      }
    if(rowBit[2] & MASK_CONS) {
      int t = 2 == last ? 1 : 0;
      MOVE(2, t);
      last = 2 == last ? 1 : 2;
      continue;
      }
    break;
    }
}

//-----------------------------------------------------
void cartella::extract(tRange *buff)
{
  int pos_row = 0;
  for(int i = 0; i < TOT_ROW; ++i, pos_row += TOT_COLUMN) {
    for(int j = 0; j < MAX_NUMB_ROW; ++j) {
      int column = Row[i][j];
      int pos = pos_row + column;
      buff[pos] = Ten[column]->getNumber();
      }
    }
}
//-----------------------------------------------------
void cartella::make(tRange *buff)
{
  memset(buff, 0, sizeof(*buff) * TOT_CART);
  PosTmp = 0;
  Tot = TOT_COLUMN;
  for(int i = 0; i < TOT_COLUMN; ++i) {
    Numb[i] = i;
    Ten[i]->shuffle();
    }

  for(int i = 0; i < TOT_ROW; ++i)
    makeRow(Row[i]);

  checkIfTooNear();

  extract(buff);

}
//-------------------------------------------------------
//-------------------------------------------------------
//-------------------------------------------------------
#define RESULT "Result.txt"
Gest_Cart::Gest_Cart() : Curr(0), File(0)
{
  C = new cartella;
}
//-------------------------------------------------------
Gest_Cart::~Gest_Cart()
{
  if(File)
    fclose(File);
  delete C;
}
//-------------------------------------------------------
#define BREAK1 "---------------"
#define BREAK2 "---------------\r\n"
bool Gest_Cart::run(int numCart, bool tabbed, int seme)
{
  if(File)
    fclose(File);
  File = fopen(RESULT, "wb+");
  if(!seme)
    randomize();
  else
    srand(seme);
  Curr=0;
//#define MAKE_FIELD
#ifdef MAKE_FIELD
  if(tabbed) {
    char t[6] = "N";
    int i;
    for(i = 0; i < TOT_CART-1; ++i) {
      itoa(i+1,t+1,10);
      strcat(t,"\t");
      fwrite(t, strlen(t), 1, File);
      }
    itoa(i+1,t+1,10);
    strcat(t,"\r\n");
    fwrite(t, strlen(t), 1, File);
    }
#endif
  for(int i = 0; i < numCart; ++i) {
    ++Curr;
    showCurr(Curr);
    tRange numb[TOT_CART+1];
    C->make(numb);
    if(tabbed)
      saveTabbed(numb);
    else
      save(numb);
    if(get_break()) {
      fclose(File);
      return false;
      }
    }
  fclose(File);
  return true;
}
//-------------------------------------------------------
void Gest_Cart::save(tRange *numb)
{
    char buff[100];
    int pos=0;
    sprintf(buff, "%s[%4d]%s", BREAK1, Curr, BREAK2);
    fwrite(buff, strlen(buff), 1, File);
    for(int i = 0;i < TOT_ROW; ++i) {
      for(int j=0; j < TOT_COLUMN; ++j,++pos) {
        if(numb[pos])
          sprintf(buff,"%4d",static_cast<int>(numb[pos]));
        else
          sprintf(buff,"    ");
        fwrite(buff, strlen(buff), 1, File);
        }
      fwrite("\r\n", 2, 1, File);
      }
    sprintf(buff,"%s------%s",BREAK1,BREAK2);
    fwrite(buff, strlen(buff), 1, File);
}
//-------------------------------------------------------
void Gest_Cart::saveTabbed(tRange *numb)
{
    char buff[100];
    int pos=0;
    for(int i = 0;i < TOT_ROW; ++i) {
      for(int j=0; j < TOT_COLUMN; ++j,++pos) {
        if(numb[pos])
          sprintf(buff,"%d\t",static_cast<int>(numb[pos]));
        else
          sprintf(buff," \t");
        fwrite(buff, strlen(buff), 1, File);
        }
      }
    fwrite("\r\n", 2, 1, File);
}
//-------------------------------------------------------