import 'whatwg-fetch';
import { observable } from "mobx";
import CollectionDB from './CollectionDB';
import spreadsheet_data from './meta_spreadsheet.json';
import { Card } from '../common/definitions';

type card_type = 'attacks' | 'battlegear' | 'creatures' | 'locations' | 'mugic';

type data_type = 'cards' | 'portal';

function retry (fn: () => any, retries=0, err=null) {
  if (retries < 0) {
    return Promise.reject(err);
  }
  return fn().catch((err: any) => {
    return retry(fn, (retries - 1), err);
  });
}

class API {
    @observable portal;
    @observable cards;
    @observable urls;
    private static instance: API;
  
    get base_image() { return "https://drive.google.com/uc?id=" }
    get thumb_missing() { return "1JYjPzkv74IhzlHTyVh2niTDyui73HSfp" }
    get card_back() { return "https://i.imgur.com/xbeDBRJ.png" }
    // such secure, much wow
    get key() { 
      return ["AIz", "aSy", "Bfq", "09-", "tBi", "78b", "nH6", "6f1", "Lkn", "zGD", "XM9", "Zu9", "JG0"].join("");
    }
  
    private constructor () {
      // This sets up urls and kicks off db
      try {
        const urls = {};
        spreadsheet_data.forEach(({ type, subtype, url }) => {
          if (!urls[type]) urls[type] = {};
          urls[type][subtype] = url;
        });
        this.urls = urls;
      
        this.portal = new CollectionDB(this, 'portal');
        this.cards = new CollectionDB(this, 'cards');
      }
      catch (err) {
        this.portal = null;
        this.cards = null;
        this.urls = null;
        console.error('setting up database failed', err);
      }
    }

    // Singleton
    static getInstance(): API {
      if (!API.instance) { API.instance = new API() }
      return API.instance;
    }
    
    path(spreadsheetID: string) {
      return `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetID}/values/Sheet1?key=${this.key}`;
    }
    
    async getSpreadsheetTime(spreadsheetId: string) {
      const url = `https://content.googleapis.com/drive/v3/files/${spreadsheetId}?fields=modifiedTime&key=${this.key}`;
      const response = await fetch(url);

      if (response.status === 200) {
        const json = await response.json();
        if ("modifiedTime" in json) {
          return Promise.resolve(json.modifiedTime); 
        }
      } 
      return Promise.reject();
    }

    async getSpreadsheetData(spreadsheetId: string, doRetry: boolean = false) {
      const url = this.path(spreadsheetId);

      const cmd = async () => {
        const response = await fetch(url);

        if (response.status === 404) {
          throw new Error("Can't Open File");
        }

        try {
          const json = await response.json();
          return json.values;
        } catch (err) {
          throw new Error(err);
        }
      };

      return retry(cmd, doRetry ? 3 : 0);
    }
  
    // Wrapper that transforms spreadsheet data into expected object
    async parseSpreadsheetData(spreadsheetId: string, cardType: string, doRetry: boolean = false) {
      return this.getSpreadsheetData(spreadsheetId, doRetry)
      .then((data: Array<Array<string>>) => {
        if (data.length < 2) return [];

        const header = data.shift()!.map((h: string) => h.toLowerCase().replace(" ", ""));
        const cards = data.map((card: string[]) => {
          const obj = { "gsx$type": cardType };
  
          for (let i = 0; i < header.length; i++) {
            obj[`gsx$${header[i]}`] = card[i];
          }
  
          return obj;
        });

        return cards;
      });
    }
    
    // Input format
    // [{cards: 'attacks'}, {portal: 'attacks'}]
    async LoadDB(input: { [key in data_type]?: card_type }[]) {
      if (this.urls && this.portal && this.cards) {
        return Promise.all(input.map((item) => {
          return new Promise((resolve, reject) => {
            if ('cards' in item) {
              return this.cards!.setupType(item.cards, resolve, reject);
            }
            else if ('portal' in item) {
              return this.portal!.setupType(item.portal, resolve, reject);
            }
            else {
              console.error('key must be cards or portal');
              return reject();
            }
          });
        }));
      }
      else return Promise.reject();
    }
    
    /* Wrappers for images */
    cardImage(card: Card) {
      if (card.gsx$ic && card.gsx$ic !== '') {
        return card.gsx$ic;
      } else if (card.gsx$image && card.gsx$image !== '') {
        return this.base_image + card.gsx$image;
      } else {
        return this.card_back;
      }
    }

    hasFullart = (card: Card) => (
      Boolean(card.gsx$if !== undefined && card.gsx$if !== '') ||
      Boolean(card.gsx$splash !== undefined && card.gsx$splash !== '') ||
      Boolean(card.gsx$alt !== undefined && card.gsx$alt !== '')
    );

    /* Wrapper for full art */
    cardFullart = (card: Card) => {
      if (card.gsx$if && card.gsx$if !== '') {
        return card.gsx$if;
      } else if (card.gsx$splash && card.gsx$splash !== '') {
        return this.base_image + card.gsx$splash;
      } else if (card.gsx$alt) { 
        return card.gsx$alt;
      } else {
        return this.card_back;
      }
    };
  
    get tribes() {
      return ["Danian", "Generic", "Mipedian", "M'arrillian", "OverWorld", "UnderWorld"];
    }
  
    // For the conversion of shorthand in database
    get sets() {
      return {
        "DOP": "Dawn of Perim",
        "ZOTH": "Zenith of the Hive",
        "SS": "Silent Sands",
        "MI": "Beyond the Doors",
        "ROTO": "Rise of the Oligarch",
        "TOTT": "Turn of the Tide",
        "FUN": "Forged Unity",
        "AU": "Alliance Unraveled",
        "FAS": "Fire and Stone",
        "OP1": "Organized Play 1",
        "PE1": "Premium Edition 1",
        "SAS": "Storm and Sea",
        "EE": "Elemental Emperors",
        "BR": "Beyond Rare",
        "LR": "League Rewards",
        "PROMO": "Promotional",
        "PROTO": "Prototype"
      };
    }
  
    purgeDB() {
      if (this.cards) this.cards.purgeDB();
      if (this.portal) this.portal.purgeDB();
      setTimeout(() => {
        window.location.reload();
      }, 300);
    }
  
}

export default API;
