//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//

import { Account, AnyAttribute, Class, Doc, DocIndexState, IndexKind, Obj, Ref } from './classes'
import { FindResult } from './storage'
import core from './component'

function toHex (value: number, chars: number): string {
  const result = value.toString(16)
  if (result.length < chars) {
    return '0'.repeat(chars - result.length) + result
  }
  return result
}

let counter = (Math.random() * (1 << 24)) | 0
const random = toHex((Math.random() * (1 << 24)) | 0, 6) + toHex((Math.random() * (1 << 16)) | 0, 4)

function timestamp (): string {
  const time = (Date.now() / 1000) | 0
  return toHex(time, 8)
}

function count (): string {
  const val = counter++ & 0xffffff
  return toHex(val, 6)
}

/**
 * @public
 * @returns
 */
export function generateId<T extends Doc> (): Ref<T> {
  return (timestamp() + random + count()) as Ref<T>
}

let currentAccount: Account

/**
 * @public
 * @returns
 */
export function getCurrentAccount (): Account {
  return currentAccount
}

/**
 * @public
 * @param account -
 */
export function setCurrentAccount (account: Account): void {
  currentAccount = account
}
/**
 * @public
 */
export function escapeLikeForRegexp (value: string): string {
  return value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
}

/**
 * @public
 */
export function toFindResult<T extends Doc> (docs: T[], total?: number): FindResult<T> {
  const length = total ?? docs.length
  return Object.assign(docs, { total: length })
}

/**
 * @public
 */
export interface WorkspaceId {
  name: string
  productId: string
}

/**
 * @public
 *
 * Combine workspace with productId, if not equal ''
 */
export function getWorkspaceId (workspace: string, productId: string = ''): WorkspaceId {
  return {
    name: workspace,
    productId
  }
}

/**
 * @public
 */
export function toWorkspaceString (id: WorkspaceId, sep = '@'): string {
  return id.name + (id.productId === '' ? '' : sep + id.productId)
}

const attributesPrefix = 'attributes.'

/**
 * @public
 */
export interface IndexKeyOptions {
  _class?: Ref<Class<Obj>>
  docId?: Ref<DocIndexState>
  extra?: string[]
}
/**
 * @public
 */

export function docUpdKey (name: string, opt?: IndexKeyOptions): string {
  return attributesPrefix + docKey(name, opt)
}
/**
 * @public
 */
export function docKey (name: string, opt?: IndexKeyOptions): string {
  const extra = opt?.extra !== undefined && opt?.extra?.length > 0 ? `#${opt.extra?.join('#') ?? ''}` : ''
  return (
    (opt?.docId !== undefined ? opt.docId.split('.').join('_') + '|' : '') +
    (opt?._class === undefined ? name : `${opt?._class}%${name}${extra}`)
  )
}

/**
 * @public
 */
export function extractDocKey (key: string): {
  _class?: Ref<Class<Doc>>
  attr: string
  docId?: Ref<DocIndexState>
  extra: string[]
} {
  let k = key
  if (k.startsWith(attributesPrefix)) {
    k = k.slice(attributesPrefix.length)
  }
  let docId: Ref<DocIndexState> | undefined
  let _class: Ref<Class<Doc>> | undefined
  let attr = ''
  const docSepPos = k.indexOf('|')
  if (docSepPos !== -1) {
    docId = k.substring(0, docSepPos).replace('_', '.') as Ref<DocIndexState>
    k = k.substring(docSepPos + 1)
  }
  const clPos = k.indexOf('%')
  if (clPos !== -1) {
    _class = k.substring(0, clPos) as Ref<Class<Doc>>
    attr = k.substring(clPos + 1)
  } else {
    attr = k
  }
  const extra = attr.split('#')
  attr = extra.splice(0, 1)[0]

  return { docId, attr, _class, extra }
}

/**
 * @public
 */
export function isFullTextAttribute (attr: AnyAttribute): boolean {
  return attr.index === IndexKind.FullText || attr.type._class === core.class.TypeAttachment
}

/**
 * @public
 */
export interface IdMap<T extends Doc> extends Map<Ref<T>, T> {}

/**
 * @public
 */
export function toIdMap<T extends Doc> (arr: T[]): IdMap<T> {
  return new Map(arr.map((p) => [p._id, p]))
}

/**
 * @public
 */
export function concatLink (host: string, path: string): string {
  if (!host.endsWith('/') && !path.startsWith('/')) {
    return `${host}/${path}`
  } else if (host.endsWith('/') && path.startsWith('/')) {
    const newPath = path.slice(1)
    return `${host}${newPath}`
  } else {
    return `${host}${path}`
  }
}
