/******************************************************************************
 * Copyright © 2014-2017 The SuperNET Developers.                             *
 *                                                                            *
 * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at                  *
 * the top-level directory of this distribution for the individual copyright  *
 * holder information and the developer policies on copyright and licensing.  *
 *                                                                            *
 * Unless otherwise agreed in a custom licensing agreement, no part of the    *
 * SuperNET software, including this file may be copied, modified, propagated *
 * or distributed except according to the terms contained in the LICENSE file *
 *                                                                            *
 * Removal or modification of this copyright notice is prohibited.            *
 *                                                                            *
 ******************************************************************************/
//
//  LP_scan.c
//  marketmaker
//


int32_t LP_blockinit(struct iguana_info *coin,int32_t height)
{
    int32_t i,iter,numtx,checkht=-1; cJSON *blockobj,*txs,*txobj; bits256 txid; struct LP_transaction *tx;
    portable_mutex_lock(&LP_blockinit_mutex);
    if ( (blockobj= LP_blockjson(&checkht,coin->symbol,0,height)) != 0 && checkht == height )
    {
        if ( (txs= jarray(&numtx,blockobj,"tx")) != 0 )
        {
            //printf("LP_blockinit %s ht.%d numtx.%d\n",coin->symbol,height,numtx);
            for (iter=0; iter<2; iter++)
            {
                txobj = 0;
                for (i=0; i<numtx; i++)
                {
                    txid = jbits256i(txs,i);
                    if ( (tx= LP_transactionfind(coin,txid)) != 0 )
                    {
                        if ( tx->height == 0 )
                            tx->height = height;
                        else if ( tx->height != height )
                        {
                            printf("LP_blockinit: tx->height %d != %d\n",tx->height,height);
                            tx->height = height;
                        }
                        if ( iter == 1 )
                            txobj = LP_transactioninit(coin,txid,iter,0);
                    } else txobj = LP_transactioninit(coin,txid,iter,0);
                    if ( txobj != 0 )
                        free_json(txobj), txobj = 0;
                }
            }
        }
        free_json(blockobj);
    }
    portable_mutex_unlock(&LP_blockinit_mutex);
    if ( checkht == height )
        return(0);
    else return(-1);
}

int32_t LP_scanblockchain(struct iguana_info *coin,int32_t startheight,int32_t endheight)
{
    int32_t ht,n = 0;
    for (ht=startheight; ht<=endheight; ht++)
    {
        if ( LP_blockinit(coin,ht) < 0 )
        {
            printf("error loading block.%d of (%d, %d)\n",ht,startheight,endheight);
            return(ht-1);
        }
        n++;
        if ( (n % 1000) == 0 )
            printf("%.1f%% ",100. * (double)n/(endheight-startheight+1));
    }
    return(endheight);
}

char *banned_txids[] =
{
    "78cb4e21245c26b015b888b14c4f5096e18137d2741a6de9734d62b07014dfca", //233559
    "00697be658e05561febdee1aafe368b821ca33fbb89b7027365e3d77b5dfede5", //234172
    "e909465788b32047c472d73e882d79a92b0d550f90be008f76e1edaee6d742ea", //234187
    "f56c6873748a327d0b92b8108f8ec8505a2843a541b1926022883678fb24f9dc", //234188
    "abf08be07d8f5b3a433ddcca7ef539e79a3571632efd6d0294ec0492442a0204", //234213
    "3b854b996cc982fba8c06e76cf507ae7eed52ab92663f4c0d7d10b3ed879c3b0", //234367
    "fa9e474c2cda3cb4127881a40eb3f682feaba3f3328307d518589024a6032cc4", //234635
    "ca746fa13e0113c4c0969937ea2c66de036d20274efad4ce114f6b699f1bc0f3", //234662
    "43ce88438de4973f21b1388ffe66e68fda592da38c6ef939be10bb1b86387041", //234697
    "0aeb748de82f209cd5ff7d3a06f65543904c4c17387c9d87c65fd44b14ad8f8c", //234899
    "bbd3a3d9b14730991e1066bd7c626ca270acac4127131afe25f877a5a886eb25", //235252
    "fa9943525f2e6c32cbc243294b08187e314d83a2870830180380c3c12a9fd33c", //235253
    "a01671c8775328a41304e31a6693bbd35e9acbab28ab117f729eaba9cb769461", //235265
    "2ef49d2d27946ad7c5d5e4ab5c089696762ff04e855f8ab48e83bdf0cc68726d", //235295
    "c85dcffb16d5a45bd239021ad33443414d60224760f11d535ae2063e5709efee", //235296
    // all vouts banned
    "c4ea1462c207547cd6fb6a4155ca6d042b22170d29801a465db5c09fec55b19d", //246748
    "305dc96d8bc23a69d3db955e03a6a87c1832673470c32fe25473a46cc473c7d1", //247204
};

int32_t komodo_bannedset(int32_t *indallvoutsp,bits256 *array,int32_t max)
{
    int32_t i;
    if ( sizeof(banned_txids)/sizeof(*banned_txids) > max )
    {
        printf("komodo_bannedset: buffer too small %ld vs %d\n",(long)sizeof(banned_txids)/sizeof(*banned_txids),max);
        exit(-1);
    }
    for (i=0; i<sizeof(banned_txids)/sizeof(*banned_txids); i++)
        decode_hex(array[i].bytes,sizeof(array[i]),banned_txids[i]);
    *indallvoutsp = i-2;
    return(i);
}

int sort_balance(void *a,void *b)
{
    int64_t aval,bval;
    /* compare a to b (cast a and b appropriately)
     * return (int) -1 if (a < b)
     * return (int)  0 if (a == b)
     * return (int)  1 if (a > b)
     */
    aval = ((struct LP_address *)a)->balance;
    bval = ((struct LP_address *)b)->balance;
    //printf("%.8f vs %.8f -> %d\n",dstr(aval),dstr(bval),(int32_t)(bval - aval));
    return((aval == bval) ? 0 : ((aval < bval) ? 1 : -1));
}

// a primitive restore can be done by loading the previous snapshot and creating a virtual tx for all the balance at height-1. this wont allow anything but new snapshots, but for many use cases that is all that is needed

cJSON *LP_snapshot(struct iguana_info *coin,int32_t height)
{
   static bits256 bannedarray[64]; static int32_t numbanned,indallvouts,maxsnapht; static char lastcoin[65];
    struct LP_transaction *tx,*tmp; struct LP_address *ap,*atmp; int32_t isKMD,i,j,n,skipflag=0,startht,endht,ht; uint64_t banned_balance=0,balance=0,noaddr_balance=0; cJSON *retjson,*array,*item;
    if ( bannedarray[0].txid == 0 )
        numbanned = komodo_bannedset(&indallvouts,bannedarray,(int32_t)(sizeof(bannedarray)/sizeof(*bannedarray)));
    startht = 1;
    endht = height-1;
    if ( strcmp(coin->symbol,lastcoin) == 0 )
    {
        if ( maxsnapht > height )
            skipflag = 1;
        else startht = maxsnapht+1;
    }
    else
    {
        maxsnapht = 0;
        strcpy(lastcoin,coin->symbol);
    }
    retjson = cJSON_CreateObject();
    if ( skipflag == 0 && startht < endht )
    {
        if ( (ht= LP_scanblockchain(coin,startht,endht)) < endht )
        {
            if ( ht > maxsnapht )
            {
                maxsnapht = ht;
                printf("maxsnapht.%d for %s\n",maxsnapht,coin->symbol);
            }
            sleep(10);
            if ( (ht= LP_scanblockchain(coin,maxsnapht+1,endht)) < endht )
            {
                if ( ht > maxsnapht )
                {
                    maxsnapht = ht;
                    printf("maxsnapht.%d for %s\n",maxsnapht,coin->symbol);
                }
                jaddstr(retjson,"error","blockchain scan error");
                return(retjson);
            }
        }
        if ( ht > maxsnapht )
        {
            maxsnapht = ht;
            printf("maxsnapht.%d for %s\n",maxsnapht,coin->symbol);
        }
    }
    portable_mutex_lock(&coin->txmutex);
    portable_mutex_lock(&coin->addrmutex);
    HASH_ITER(hh,coin->addresses,ap,atmp)
    {
        ap->balance = 0;
    }
    isKMD = (strcmp(coin->symbol,"KMD") == 0) ? 1 : 0;
    HASH_ITER(hh,coin->transactions,tx,tmp)
    {
        if ( tx->height < height )
        {
            if ( isKMD != 0 )
            {
                for (j=0; j<numbanned; j++)
                    if ( bits256_cmp(bannedarray[j],tx->txid) == 0 )
                        break;
                if ( j < numbanned )
                {
                    for (i=0; i<tx->numvouts; i++)
                        banned_balance += tx->outpoints[i].value;
                    //char str[256]; printf("skip banned %s bannedtotal: %.8f\n",bits256_str(str,tx->txid),dstr(banned_balance));
                    continue;
                }
            }
            for (i=0; i<tx->numvouts; i++)
            {
                if ( (ht=tx->outpoints[i].spendheight) > 0 && ht < height )
                    continue;
                if ( tx->outpoints[i].coinaddr[0] != 0 && (ap= _LP_address(coin,tx->outpoints[i].coinaddr)) != 0 )
                {
                    balance += tx->outpoints[i].value;
                    ap->balance += tx->outpoints[i].value;
                    //printf("(%s/%s) %.8f %.8f\n",tx->outpoints[i].coinaddr,ap->coinaddr,dstr(tx->outpoints[i].value),dstr(ap->balance));
                } else noaddr_balance += tx->outpoints[i].value;
            }
        }
    }
    HASH_SORT(coin->addresses,sort_balance);
    portable_mutex_unlock(&coin->addrmutex);
    portable_mutex_unlock(&coin->txmutex);
    printf("%s balance %.8f at height.%d\n",coin->symbol,dstr(balance),height);
    array = cJSON_CreateArray();
    n = 0;
    HASH_ITER(hh,coin->addresses,ap,atmp)
    {
        if ( ap->balance != 0 )
        {
            item = cJSON_CreateObject();
            jaddnum(item,ap->coinaddr,dstr(ap->balance));
            jaddi(array,item);
            n++;
        }
    }
    jadd(retjson,"balances",array);
    jaddstr(retjson,"coin",coin->symbol);
    jaddnum(retjson,"height",height);
    jaddnum(retjson,"numaddresses",n);
    jaddnum(retjson,"total",dstr(balance));
    jaddnum(retjson,"noaddr_total",dstr(noaddr_balance));
    return(retjson);
}

char *LP_snapshot_balance(struct iguana_info *coin,int32_t height,cJSON *argjson)
{
    cJSON *snapjson,*retjson,*balances,*array,*addrs,*child,*item,*item2; char *coinaddr,*refaddr; int32_t i,n,j,m; uint64_t total=0,value,balance = 0;
    retjson = cJSON_CreateObject();
    array = cJSON_CreateArray();
    if ( (snapjson= LP_snapshot(coin,height)) != 0 )
    {
        total = jdouble(snapjson,"total") * SATOSHIDEN;
        if ( (addrs= jarray(&m,argjson,"addresses")) != 0 )
        {
            if ( (balances= jarray(&n,snapjson,"balances")) != 0 )
            {
                for (i=0; i<n; i++)
                {
                    item = jitem(balances,i);
                    if ( (child= item->child) != 0 )
                    {
                        value = (uint64_t)(child->valuedouble * SATOSHIDEN);
                        if ( (refaddr= get_cJSON_fieldname(child)) != 0 )
                        {
                            //printf("check %s %.8f against %d\n",refaddr,dstr(value),m);
                            for (j=0; j<m; j++)
                            {
                                if ( (coinaddr= jstri(addrs,j)) != 0 )
                                {
                                    if ( strcmp(coinaddr,refaddr) == 0 )
                                    {
                                        item2 = cJSON_CreateObject();
                                        jaddnum(item2,coinaddr,dstr(value));
                                        jaddi(array,item2);
                                        balance += value;
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        free_json(snapjson);
    }
    jadd(retjson,"balances",array);
    jaddstr(retjson,"coin",coin->symbol);
    jaddnum(retjson,"height",height);
    jaddnum(retjson,"balance",dstr(balance));
    jaddnum(retjson,"total",dstr(total));
    return(jprint(retjson,1));
}

char *LP_dividends(struct iguana_info *coin,int32_t height,cJSON *argjson)
{
    cJSON *array,*retjson,*item,*child,*exclude=0; int32_t i,j,emitted=0,dusted=0,n,execflag=0,flag,iter,numexcluded=0; char buf[1024],*field,*prefix="",*suffix=""; uint64_t dustsum=0,excluded=0,total=0,dividend=0,value,val,emit=0,dust=0; double ratio = 1.;
    if ( (retjson= LP_snapshot(coin,height)) != 0 )
    {
        //printf("SNAPSHOT.(%s)\n",retstr);
        if ( (array= jarray(&n,retjson,"balances")) != 0 )
        {
            if ( (n= cJSON_GetArraySize(array)) != 0 )
            {
                if ( argjson != 0 )
                {
                    exclude = jarray(&numexcluded,argjson,"exclude");
                    dust = (uint64_t)(jdouble(argjson,"dust") * SATOSHIDEN);
                    dividend = (uint64_t)(jdouble(argjson,"dividend") * SATOSHIDEN);
                    if ( jstr(argjson,"prefix") != 0 )
                        prefix = jstr(argjson,"prefix");
                    if ( jstr(argjson,"suffix") != 0 )
                        suffix = jstr(argjson,"suffix");
                    execflag = jint(argjson,"system");
                }
                for (iter=0; iter<2; iter++)
                {
                    for (i=0; i<n; i++)
                    {
                        flag = 0;
                        item = jitem(array,i);
                        if ( (child= item->child) != 0 )
                        {
                            value = (uint64_t)(child->valuedouble * SATOSHIDEN);
                            if ( (field= get_cJSON_fieldname(child)) != 0 )
                            {
                                for (j=0; j<numexcluded; j++)
                                    if ( strcmp(field,jstri(exclude,j)) == 0 )
                                    {
                                        flag = 1;
                                        break;
                                    }
                            }
                            //printf("(%s %s %.8f) ",jprint(item,0),field,dstr(value));
                            if ( iter == 0 )
                            {
                                if ( flag != 0 )
                                    excluded += value;
                                else total += value;
                            }
                            else
                            {
                                if ( flag == 0 )
                                {
                                    val = ratio * value;
                                    if ( val >= dust )
                                    {
                                        sprintf(buf,"%s %s %.8f %s",prefix,field,dstr(val),suffix);
                                        if ( execflag != 0 )
                                        {
                                            if ( system(buf) != 0 )
                                                printf("error system.(%s)\n",buf);
                                        }
                                        else printf("%s\n",buf);
                                        emit += val;
                                        emitted++;
                                    } else dustsum += val, dusted++;
                                }
                            }
                        }
                    }
                    if ( iter == 0 )
                    {
                        if ( total > 0 )
                        {
                            if ( dividend == 0 )
                                dividend = total;
                            ratio = (double)dividend / total;
                        } else break;
                    }
                }
            }
        }
        free_json(retjson);
        retjson = cJSON_CreateObject();
        jaddstr(retjson,"coin",coin->symbol);
        jaddnum(retjson,"height",height);
        jaddnum(retjson,"total",dstr(total));
        jaddnum(retjson,"emitted",emitted);
        jaddnum(retjson,"excluded",dstr(excluded));
        if ( dust != 0 )
        {
            jaddnum(retjson,"dust",dstr(dust));
            jaddnum(retjson,"dusted",dusted);
        }
        if ( dustsum != 0 )
            jaddnum(retjson,"dustsum",dstr(dustsum));
        jaddnum(retjson,"dividend",dstr(dividend));
        jaddnum(retjson,"dividends",dstr(emit));
        jaddnum(retjson,"ratio",ratio);
        if ( execflag != 0 )
            jaddnum(retjson,"system",execflag);
        /*if ( prefix[0] != 0 )
            jaddstr(retjson,"prefix",prefix);
        if ( suffix[0] != 0 )
            jaddstr(retjson,"suffix",suffix);*/
        return(jprint(retjson,1));
    }
    return(clonestr("{\"error\":\"symbol not found\"}"));
}

int32_t LP_spendsearch(char *coinaddr,bits256 *spendtxidp,int32_t *indp,char *symbol,bits256 searchtxid,int32_t searchvout)
{
    struct LP_transaction *tx; struct iguana_info *coin;
    *indp = -1;
    if ( (coin= LP_coinfind(symbol)) == 0 || coin->inactive != 0 )
        return(-1);
    memset(spendtxidp,0,sizeof(*spendtxidp));
    if ( (tx= LP_transactionfind(coin,searchtxid)) != 0 )
    {
        if ( searchvout < tx->numvouts && tx->outpoints[searchvout].spendvini >= 0 )
        {
            *spendtxidp = tx->outpoints[searchvout].spendtxid;
            *indp = tx->outpoints[searchvout].spendvini;
            LP_swap_getcoinaddr(symbol,coinaddr,*spendtxidp,*indp);
            return(tx->outpoints[searchvout].spendheight);
        }
    }
    return(-1);
}

int32_t LP_mempoolscan(char *symbol,bits256 searchtxid)
{
    int32_t i,n; cJSON *array,*txobj; bits256 txid,zero; struct iguana_info *coin; struct LP_transaction *tx;
    if ( (coin= LP_coinfind(symbol)) == 0 || coin->inactive != 0 || coin->electrum != 0 )
        return(-1);
    memset(zero.bytes,0,sizeof(zero));
    if ( (array= LP_getmempool(symbol,0,searchtxid,zero)) != 0 )
    {
        if ( is_cJSON_Array(array) != 0 && (n= cJSON_GetArraySize(array)) > 0 )
        {
            for (i=0; i<n; i++)
            {
                txid = jbits256i(array,i);
                if ( (tx= LP_transactionfind(coin,txid)) == 0 )
                {
                    txobj = LP_transactioninit(coin,txid,0,0);
                    txobj = LP_transactioninit(coin,txid,1,txobj);
                    if ( txobj != 0 )
                        free_json(txobj);
                }
                if ( bits256_cmp(txid,searchtxid) == 0 )
                {
                    char str[65]; printf("found %s tx.(%s) in mempool slot.%d\n",symbol,bits256_str(str,txid),i);
                    return(i);
                }
            }
        }
        free_json(array);
    }
    return(-1);
}

int32_t LP_waitmempool(char *symbol,char *coinaddr,bits256 txid,int32_t vout,int32_t duration)
{
    struct iguana_info *coin; bits256 zero; cJSON *array,*item; uint32_t expiration,i,n; int32_t num,numconfirms = -1;
    if ( (coin= LP_coinfind(symbol)) == 0 || coin->inactive != 0 )
    {
        printf("LP_waitmempool missing coin.%p or inactive\n",coin);
        return(-1);
    }
    expiration = (uint32_t)time(NULL) + duration;
    while ( LP_STOP_RECEIVED == 0 )
    {
        if ( LP_gettx_presence(&num,symbol,txid,coinaddr) != 0 )
            numconfirms = num;
        else
        {
            if ( coin->electrum == 0 )
            {
                if ( LP_mempoolscan(symbol,txid) >= 0 )
                    numconfirms = 0;
            }
            else
            {
                memset(zero.bytes,0,sizeof(zero));
                if ( (array= electrum_address_getmempool(symbol,coin->electrum,&array,coinaddr,txid,zero)) != 0 )
                {
                    //char str[65]; printf("check %s mempool.(%s)\n",bits256_str(str,txid),jprint(array,0));
                    if ( (n= cJSON_GetArraySize(array)) > 0 )
                    {
                        for (i=0; i<n; i++)
                        {
                            item = jitem(array,i);
                            if ( bits256_cmp(txid,jbits256(item,"tx_hash")) == 0 )
                            {
                                char str[65]; printf("found %s %s in mempool\n",symbol,bits256_str(str,txid));
                                numconfirms = 0;
                                break;
                            }
                        }
                    }
                    free(array);
                }
                LP_listunspent_issue(coin->symbol,coinaddr,1,txid,zero);
                struct LP_address_utxo *up;
                if ( (up= LP_address_utxofind(coin,coinaddr,txid,vout)) != 0 )
                {
                    char str[65]; printf("address_utxofind found confirmed %s %s %s ht.%d vs %d\n",symbol,coinaddr,bits256_str(str,txid),up->U.height,coin->height);
                    if ( coin->electrum != 0 && (array= electrum_address_gethistory(symbol,coin->electrum,&array,coinaddr,txid)) != 0 )
                        free_json(array);
                    if ( coin->height >= up->U.height )
                        numconfirms = (coin->height - up->U.height + 1);
                }
            }
        }
        if ( time(NULL) > expiration || numconfirms >= 0 )
            break;
        sleep(10);
    }
    //if ( numconfirms <= 0 )
    //    numconfirms = LP_numconfirms(symbol,coinaddr,txid,vout,1); // no, no recursion occurs!
    return(numconfirms);
}

int32_t LP_mempool_vinscan(bits256 *spendtxidp,int32_t *spendvinp,char *symbol,char *coinaddr,bits256 searchtxid,int32_t searchvout,bits256 searchtxid2,int32_t searchvout2)
{
    struct iguana_info *coin; int32_t selector; cJSON *array; char addr[64];
    if ( symbol == 0 || symbol[0] == 0 || bits256_nonz(searchtxid) == 0 || bits256_nonz(searchtxid2) == 0 )
        return(-1);
    if ( (coin= LP_coinfind(symbol)) == 0 || coin->inactive != 0 )
        return(-1);
    if ( time(NULL) > coin->lastmempool+LP_MEMPOOL_TIMEINCR )
    {
        if ( (array= LP_getmempool(symbol,coinaddr,searchtxid,searchtxid2)) != 0 )
        {
            free_json(array);
            coin->lastmempool = (uint32_t)time(NULL);
        }
    }
    if ( (selector= LP_spendsearch(addr,spendtxidp,spendvinp,symbol,searchtxid,searchvout)) >= 0 )
        return(selector);
    else if ( (selector= LP_spendsearch(addr,spendtxidp,spendvinp,symbol,searchtxid2,searchvout2)) >= 0 )
        return(selector);
    return(-1);
}