#!/usr/bin/env python3

# MIT License

# Copyright (c) 2021 subopt.org

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from gql import gql, Client
from gql.transport.requests import RequestsHTTPTransport
import datetime as dt
import time
from time import sleep
import matplotlib
import matplotlib.pyplot as plt
import pickle
import os
import calendar
from calendar import timegm
import datetime
import argparse
from dateutil.relativedelta import relativedelta
from copy import deepcopy

def t2s(ts):
    return datetime.datetime.utcfromtimestamp(int(ts)).strftime('%Y-%m-%dT%H:%M:%SZ')

def exec_punk_query(query):
    sample_transport=RequestsHTTPTransport(
        url='https://api.studio.thegraph.com/query/5286/cryptopunks/1',
#        url='https://api.thegraph.com/subgraphs/name/itsjerryokolo/cryptopunks',
        verify=True,
        retries=3,
    )
    client = Client(
        transport=sample_transport
    )
    return client.execute(query)

# removes any duplicate transactions, based on the ID field.
def dedup(txns):
    clean_txns = []
    all_ids = {}
    for txn in txns:
        if txn['id'] in all_ids:
            continue
        else:
            clean_txns.append(txn)        
            all_ids[txn['id']] = True            
    return clean_txns

# This justs gets the IDs of all possibly relevant transactions.
# It doesn't get the details of those transactions.
def get_latest_txns(dry_run=False):
    state = {}
    d = "1498017600" # approximate time of the first cryptopunk transaction
    txns = []
    
    if os.path.exists("state.pkl"):
        print("Starting from existing transaction list")
        state = pickle.load(open("state.pkl","rb"))
        d = state['d']
        txns = state['txns']
    else:
        print("initializing new transaction list")
        
    print("So far have {} txns. Getting all new txns since {}".format(len(txns),d))
    while True:
        #Note: using gte here can result in duplicates since we will probably get
        #the final transaction at t-1 at the beginning of our query at time t.
        #but if we use gt, then there is a very small chance we can miss transactions.
        #this would happen if a block has multiple punk transactions and 
        #and it so happens that this block occupies the last block in a group of 1000 txns
        q = None
        try:
            querystr = '''{
            transactions(  orderBy: date, 
                           where: {date_gte: "%s", punk_not: ""},
                           first: 1000, skip: %s  ){
              id
              date
              block  # done
            }}''' % (d,0)

            query4 = gql(querystr)
            q = exec_punk_query(query4)['transactions']
        except:
            print("Error with your query {}".format(querystr))
            raise
        sleep(1) # avoid being throttled
        d=q[-1]['date']
        print("First date was {} and last date was {} ({})".format(q[0]['date'],d,t2s(d)))

        txns.extend(q)
   
        print("Extended txns by {} for a total of {}".format(len(q),len(txns)))
        if(len(q) < 1000):
            print("Got all txns")
            break

    print("Before deduplication, got {} transactions".format(len(txns)))
    txns=dedup(txns)
    print("After deduplication, got {} transactions. Final date was {}".format(len(txns), d))
    state['d'] = d
    state['txns'] = txns
    if not dry_run:
        pickle.dump(state,open("state.pkl","wb"))
    return txns

def get_latest_txn_details(dry_run=False):
    state = pickle.load(open("state.pkl","rb"))
    txns = state['txns']
    txn_details = []
    if 'txn_details' in state:
        print("Extending existing transaction details array")
        txn_details = state['txn_details']
    else:
        print("Getting transaction details for the first time")
    print("We have details on {} of {} txns. Getting new ones".format(len(txn_details),len(txns)))
    for i in range(len(txn_details),len(txns)):
        txn = txns[i]
        query4 = gql('''{
          transactions( block: { number: %s}, 
                        orderBy: date, 
                        where : {id: "%s", date: "%s", punk_not: ""}){
            id
            owner{
             id
             punk
            }
            ctoken
            punkTransfers
            punk {
             id
             owner{
               id
             }
             transferedTo
             assignedTo{
               id
             }
             purchasedBy{
               id
             }
             bid{
              bid
              transaction{
                  block
              }
             }
             offer{
               id
               amountOffered
             }
             purchase{
              amount
              seller
              id
             }
             punkTransfer
            }
            assigned
            offer
            {
             id
             offeredBy
             amountOffered
            }
        
            bid #done
            {
             id
             owner 
             punk
             bidder
             bid
             transaction{
                  block
              }
            }
            date   # done
            block  # done
        }}''' % (txn['block'],txn['id'],txn['date']))
        try:
            q=exec_punk_query(query4)['transactions']
        except:
            print("Error executing query. run this function again to finish fetching transactions")
            #exit()
            
        if (i % 1000 == 0) and (not dry_run):
                print("Checkpointing at {}".format(i))
                state['txn_details'] = txn_details
                pickle.dump(state,open("state.pkl","wb"))
        txn_details.extend(q)
        print(len(q),len(txn_details),"id:", txn['id'], "block:",txn['block'],"date:",t2s(txn['date']))
   
    state['txn_details'] = txn_details
    if not dry_run:
        pickle.dump(state,open("state.pkl","wb"))
    
    return txn_details

def get_dates_and_prices():
    bids = {}
    offers = {}
    prices=[]
    dates=[]
    punkids=[]
    for txn in txn_details:

        if txn['punk'] == None:
            continue
        elif txn['punk']['purchase'] == None:
            print("No sale",txn)
            continue
        else: #we have a punk field and punk[purchase] field
            punkid = txn['punk']['id']
            purchase = txn['punk']['purchase']
            amount = int(purchase[0]['amount'])
            if amount == 0: 
                bid = txn['punk']['bid']
                if len(bid) == 0: #These were weird transactions where the punk was offerd for 0(?)
                    amount = 0 
                else:
                    amount = int(bid[0]['bid'])

            thisdate=dt.datetime.utcfromtimestamp(float(txn['date']))
            price=amount/1000000000000000000
            ##print("At {} on {} Punk {} sold for {}Ξ".format(txn['id'],thisdate,punkid,price))
            prices.append(price)
            dates.append(thisdate)
            punkids.append(punkid)
    print("Returning {} sales".format(len(prices)))
    return dates, prices, punkids


def total_plot(dates,prices,punkids):
    plt.figure(figsize=(20,10))
    plt.plot(dates,prices,'+')
    plt.ylim(bottom=0.01)
    plt.ylabel("Price (eth)")
    plt.xlabel("Date")
    plt.title("Punk transactions {} to {}".format(dates[0].strftime("%Y-%m-%d"),
                                                  str(datetime.datetime.utcnow().date())))
    plt.yscale('log')
    plt.grid()
    plt.savefig("priceseth.png")
    with open("priceseth.txt","w") as f:
        f.write("\n".join(["{} {} {}".format(d,i,p) for d,i,p in zip(dates,punkids,prices)]))

def plot_90_days(alldates,allprices,allpunkids):
    date = datetime.datetime.now()
    date = date + relativedelta(days=-30)

    ndates = len([ d for d in alldates if d >= date])
    dates = alldates[-ndates:]
    prices = allprices[-ndates:]
    punkids = allpunkids[-ndates:]

    print("ntxns {} min price {} max {}".format(ndates,
                                                min(prices),
                                                max(prices)))
    plt.figure(figsize=(20,10))
    plt.plot(dates,prices,'+')
    plt.ylim(bottom=max(10,min([p for p in prices if p>0])))
    plt.ylim(top=max([p for p in prices if p < 124457.06])) # Get rid of the flashloan one
    plt.ylabel("Price (eth)")
    plt.xlabel("Date")
    plt.title("Punk transactions {} to {}".format(dates[0].strftime("%Y-%m-%d"),
                                                  str(datetime.datetime.utcnow().date())))
#    plt.yscale('log')
    plt.grid()
    plt.savefig("priceseth_90.png")



parser = argparse.ArgumentParser()
parser.add_argument("--dry-run",
                    action="store_true",
                    help="Do a dry run. Don't save new data")

args = parser.parse_args()

get_latest_txns(args.dry_run)
txn_details = get_latest_txn_details(args.dry_run)
dates,prices,punkids = get_dates_and_prices()

font = {'size'   : 22}
matplotlib.rc('font', **font)

total_plot(dates,prices,punkids)
plot_90_days(dates,prices,punkids)
