import moment from 'moment';
import { format } from 'timeago.js';
import { cloneDeep } from 'lodash';
import store from 'store';
import { i18n, app } from 'config';
import handle from 'core/libs/errorhandler'
import request from 'request'
import html2canvas from 'html2canvas'
import platform from 'platform'
import path from 'path'
import fs from 'fs'
import * as utils from '@ragestudio/nodecore-utils'

const { pathToRegexp } = require('path-to-regexp');

export const languages = i18n ? i18n.languages.map(item => item.key) : [];
export const defaultLanguage = i18n ? i18n.defaultLanguage : '';

import * as libs from './libs'

export * from '@ragestudio/nodecore-utils'
export const package_json = require('../../package.json');
export const GUID = app.guid;

export const clientInfo = {
  buildStable: getBuild()["stable"],
  packageName: package_json.name,
  packageStage: package_json.stage,
  siteName: app.siteName,
  version: package_json.version,
  logo: app.FullLogoPath,
  logo_dark: app.DarkFullLogoPath,
  os: platform.os,
  layout: platform.layout
};

export function getCircularReplacer() {
  const seen = new WeakSet();
  return (key, value) => {
    if (typeof value === "object" && value !== null) {
      if (seen.has(value)) {
        return { __cycle_flag: true }
      }
      seen.add(value)
    }
    return value
  }
}

export function __proto__filterSchematizedArray(data) {
  let tmp = []
  return new Promise(resolve => {
    data.forEach(async (element) => {
      if (typeof (element.require) !== 'undefined') {
        const validRequire = await window.requireQuery(element.require)
        validRequire ? tmp.push(element) : null
      } else {
        tmp.push(element)
      }
    })
    resolve(tmp)
  })
}

export function decycle(obj, stack = []) {
  if (!obj || typeof obj !== 'object')
    return obj;

  if (stack.includes(obj)) {
    return { __cycle_flag: true }
  }

  let s = stack.concat([obj]);

  return Array.isArray(obj)
    ? obj.map(x => decycle(x, s))
    : Object.fromEntries(
      Object.entries(obj)
        .map(([k, v]) => [k, decycle(v, s)]));
}

export function getBuild() {
  let build = {
    stable: false
  }
  try {
    if (!window.__build) {
      return false
    }
    const buildPath = path.resolve(__dirname, "./dist")
    const data = JSON.parse(fs.readFileSync(`${buildPath}/build.json`))
    if (typeof (data) !== "undefined" && Array.isArray(data)) {
      utils.__legacy____legacy__objectToArray(data).forEach((e) => {
        build[e.key] = e.value
      })
    }
  } catch (error) {
    // tf this is not a build sorry
  }
  return build
}

export function queryIndexer(array, callback, params) {
  if (!array) return false

  if (Array.isArray(array)) {
    let opt = {
      regex: /:id/gi
    }

    if (params) {
      opt = { ...opt, ...params }
    }

    array.forEach((e) => {
      if (e.match != null && e.to != null) {
        const pathMatch = pathMatchRegexp(e.match, window.location.pathname)
        if (pathMatch != null) {
          return callback(e.to.replace(opt.regex, pathMatch[1]))
        }
      }
    })
  }
}

export function createScreenshotFromElement(element) {
  if (!element) return false
  html2canvas(element, {
    useCORS: true,
    proxy: "localhost:8000",
    scale: 4,
    backgroundColor: "transparent"
  }).then(canvas => {
    downloadEncodedURI({ data: canvas.toDataURL() })
  })
}

export function generatePostURI(id) {
  if (window.location.origin && id) {
    return `${window.location.origin}/post/${id}`
  }
  return null
}

export function writeToClipboard(text) {
  navigator.clipboard.writeText(text)
    .then(() => {
      libs.ui.notify.info('Copy to clipboard')
    }, () => {
      /* failure */
    })
}

// [Experimental], not in use
export function getGlobals(params, callback) {
  if (!params || !params.server) return false
  let tmpResponse = []

  let req = {
    global: "__globals",
    url: params.server
  }

  params.global ? req.global = params.global : null

  let urlString = `${req.url}/${req.global}.json`
  console.log(urlString)

  request(urlString, (error, response, body) => {
    tmpResponse = body
    callback(tmpResponse)
  })
}

export function isOs(os) {
  if (process) {
    return process.platform === os ? true : false
  } else {
    return false
  }
}

export function abbreviateCount(value) {
  let updated = value
  if (value >= 1000) {
    const suffix = ["", "k", "m"]
    let numberToSuffix = Math.floor(("" + value).length / 3)
    let divider = ""

    for (let offset = 2; offset >= 1; offset--) {
      divider = parseFloat((numberToSuffix != 0 ? (value / Math.pow(1000, numberToSuffix)) : value).toPrecision(offset))
      let firstDot = (divider + "").replace(/[^a-zA-Z 0-9]+/g, '')
      if (firstDot.length <= 2) {
        break
      }
    }
    if (divider % 1 != 0) {
      divider = divider.toFixed(1)
    }
    updated = divider + suffix[numberToSuffix]
  }
  return updated
}

export function imageToBase64(img, callback) {
  const reader = new FileReader()
  reader.addEventListener('load', () => callback(reader.result))
  reader.readAsDataURL(img)
}

export function urlToBase64(url, callback) {
  let xhr = new XMLHttpRequest();
  xhr.onload = function () {
    let reader = new FileReader();
    reader.onloadend = function () {
      callback(reader.result);
    }
    reader.readAsDataURL(xhr.response);
  };
  xhr.open('GET', url);
  xhr.setRequestHeader('Access-Control-Allow-Headers', 'Access-Control-Allow-Origin');
  xhr.setRequestHeader('Access-Control-Allow-Origin', '*');
  xhr.responseType = 'blob';
  xhr.send();
}

export function b64toBlob(b64Data, contentType = '', sliceSize = 512) {
  const byteCharacters = atob(b64Data);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);

    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  const blob = new Blob(byteArrays, { type: contentType });
  return blob;
}

/**
 * Generate a download with encoded uri
 * 
 * @param {object} payload - Generation Data
 */
export function downloadDecodedURI(payload) {
  if (!payload) return false
  let { data, type, charset, filename } = payload
  /**
    * 
    * @param {object} payload - Generation Data
    */
  if (!data || !type) return false
  try {
    if (!filename) {
      filename = `${app.id}_${time.now()}.${type.split("/")[1]}`
    }
    let tmp = document.createElement('a')
    tmp.href = `data:${type};charset=${charset},${encodeURIComponent(data)}`
    tmp.download = filename
    tmp.click()
  } catch (error) {
    handle({ msg: error, code: 120 })
  }
}

export function downloadEncodedURI(payload) {
  if (!payload) return false
  let { data, filename } = payload
  /**
    * 
    * @param {object} payload - Generation Data
    */
  if (!data) return false
  try {
    if (!filename) {
      filename = `${app.id}_${time.now()}.${data.split("/")[1].split(";")[0]}`
    }
    let tmp = document.createElement('a')
    tmp.href = data
    tmp.download = filename
    tmp.click()
  } catch (error) {
    handle({ msg: error, code: 120 })
  }
}

export function GetPropertyValue(object, dataToRetrieve) {
  dataToRetrieve.split('.').forEach(function (token) {
    if (object) object = object[token];
  })
  return object;
}

/**
 * Return the last object from array
 *
 * @param array {array}
 * @return object
 */
export function objectLast(array, n) {
  if (array == null) return void 0;
  if (n == null) return array[array.length - 1];
  return array.slice(Math.max(array.length - n, 0));
}

/**
 * Object to array scheme RSA-YCORE-ARRAYPROTO.2
 *
 * @param object {object}
 * @return array
 */
export function arrayToObject(array) {
  if (!array) return false
  let tmp = []

  array.forEach((e) => {
    tmp[e.key] = e.value
  })

  return tmp
}

/**
 * Remove an element by id from an object array
 *
 * @param object {object}
 * @param id {string}
 * @return array
 */
export function objectRemoveId(object, id) {
  let arr = __legacy__objectToArray(object)
  return arr.filter((e) => {
    return e.id != id;
  });
}
/**
 * Remove an element by key from an object array
 *
 * @param object {object}
 * @param key {string}
 * @return array
 */
export function objectRemoveKey(object, key) {
  let arr = __legacy__objectToArray(object)
  return arr.filter((e) => {
    return e.key != key;
  });
}


/**
 * Global fix for convert '1, 0' to string boolean 'true, false'
 *
 * @param e {int} Numeric boolean reference
 * @return bool
 */
export function booleanFix(e) {
  if (e == 1) return true;
  return false;
}

/**
 * Request FullScreen mode
 *
 */
export function requestFullscreen() {
  var elem = document.documentElement;
  if (elem.requestFullscreen) {
    elem.requestFullscreen();
  } else if (elem.mozRequestFullScreen) {
    /* Firefox */
    elem.mozRequestFullScreen();
  } else if (elem.webkitRequestFullscreen) {
    /* Chrome, Safari and Opera */
    elem.webkitRequestFullscreen();
  } else if (elem.msRequestFullscreen) {
    /* IE/Edge */
    elem.msRequestFullscreen();
  }
}

/**
 * Handle time basic functions
 *
 */
export const time = {
  ago: a => {
    const format = moment(a).format('DDMMYYYY');
    const b = new Date(format).toLocaleString();
    return time.relativeToNow(b);
  },
  stmToAgo: a => {
    const b = a * 1000;
    return format(b);
  },
  relativeToNow: (a, b) => {
    return moment(a, b || 'DDMMYYYY').fromNow();
  },
  now: () => {
    return new Date().toLocaleString();
  }
};

export function pathMatchRegexp(regexp, pathname) {
  return pathToRegexp(regexp).exec(pathname)
}

/**
 * Query objects that specify keys and values in an array where all values are objects.
 * @param   {array}         array   An array where all values are objects, like [{key:1},{key:2}].
 * @param   {string}        key     The key of the object that needs to be queried.
 * @param   {string}        value   The value of the object that needs to be queried.
 * @return  {object|undefined}   Return frist object when query success.
 */
export function queryArray(array, key, value) {
  if (!Array.isArray(array)) {
    return;
  }
  return array.find(_ => _[key] === value);
}

/**
 * Convert an array to a tree-structured array.
 * @param   {array}     array     The Array need to Converted.
 * @param   {string}    id        The alias of the unique ID of the object in the array.
 * @param   {string}    parentId       The alias of the parent ID of the object in the array.
 * @param   {string}    children  The alias of children of the object in the array.
 * @return  {array}    Return a tree-structured array.
 */
export function arrayToTree(
  array,
  id = 'id',
  parentId = 'pid',
  children = 'children',
) {
  const result = [];
  const hash = {};
  const data = cloneDeep(array);

  data.forEach((item, index) => {
    hash[data[index][id]] = data[index];
  });

  data.forEach(item => {
    const hashParent = hash[item[parentId]];
    if (hashParent) {
      !hashParent[children] && (hashParent[children] = []);
      hashParent[children].push(item);
    } else {
      result.push(item);
    }
  });
  return result;
}

/**
 * In an array object, traverse all parent IDs based on the value of an object.
 * @param   {array}     array     The Array need to Converted.
 * @param   {string}    current   Specify the value of the object that needs to be queried.
 * @param   {string}    parentId  The alias of the parent ID of the object in the array.
 * @param   {string}    id        The alias of the unique ID of the object in the array.
 * @return  {array}    Return a key array.
 */
export function queryPathKeys(array, current, parentId, id = 'id') {
  const result = [current];
  const hashMap = new Map();
  array.forEach(item => hashMap.set(item[id], item));

  const getPath = current => {
    const currentParentId = hashMap.get(current)[parentId];
    if (currentParentId) {
      result.push(currentParentId);
      getPath(currentParentId);
    }
  };

  getPath(current);
  return result;
}

/**
 * Query which layout should be used for the current path based on the configuration.
 * @param   {layouts}     layouts   Layout configuration.
 * @param   {pathname}    pathname  Path name to be queried.
 * @return  {string}   Return frist object when query success.
 */
export function queryLayout(layouts, pathname) {
  let result = 'public';

  const isMatch = regepx => {
    return regepx instanceof RegExp
      ? regepx.test(pathname)
      : pathToRegexp(regepx).exec(pathname);
  };

  for (const item of layouts) {
    let include = false;
    let exclude = false;
    if (item.include) {
      for (const regepx of item.include) {
        if (isMatch(regepx)) {
          include = true;
          break;
        }
      }
    }

    if (include && item.exclude) {
      for (const regepx of item.exclude) {
        if (isMatch(regepx)) {
          exclude = true;
          break;
        }
      }
    }

    if (include && !exclude) {
      result = item.name;
      break;
    }
  }

  return result;
}

export function getLocale() {
  return store.get('locale') || defaultLanguage;
}

export function setLocale(language) {
  if (getLocale() !== language) {
    store.set('locale', language);
    window.location.reload();
  }
}

export function get_value(source, key) {
  if (!key || !source) return false
  try {
    const find = source.find(item => {
      return item.id === key
    })
    return find.value

  }
  catch (error) {
    return false
  }
}

export function iatToString(iat) {
  return new Date(iat * 1000).toLocaleString()
}

export function generateGUID(lenght = 6) {
  let text = ""
  const possibleChars = "abcdefghijklmnopqrstuvwxyz0123456789"

  for (let i = 0; i < 6; i++)
    text += possibleChars.charAt(Math.floor(Math.random() * possibleChars.length))

  return text
}

export function generateRandomId(length = 15) {
  return Math.random().toString(36).substring(0, length);
}