import fs from "fs";
import path from "path";
import os from "os";
import chalk from "chalk";

export interface SearchTerms {
  [index: string]: string[];
  sources: string[];
  tags: string[];
  people: string[];
}

export interface ConfigFile {
  kodi: string;
  configPath: string;
  save: () => void;
  directories: string[];
  ignoreList: string[];
  searchTerms: SearchTerms;
}

/**
 * Turns a full file path like "~/foo/bar/Rainbow.Lorikeet.2017.mp4"
 * and returns a simplified name like "rainbow lorikeet 2017 mp4" by
 * lowercasing it, swapping dots for spaces, and removing path information.
 */
export function simplifyName(name: string): string {
  return name
    .split(path.sep)
    .pop()!
    .toLowerCase()
    .replace(/\./g, " ");
}

/**
 * jA list of the default video playlist folders for each platform, relative to
 * the user's home directory.
 */
const possiblePlaylistFolders: string[][] = [
  ["AppData", "Roaming", "Kodi", "userdata", "playlists", "video"], // Win32
  [
    "AppData",
    "Local",
    "Packages",
    "XBMCFoundation.Kodi_4n2hpmxwrvr6p",
    "LocalCache",
    "Roaming",
    "Kodi",
    "userdata",
    "playlists",
    "video"
  ], // UWP
  [".kodi", "userdata", "playlists", "video"],
  ["Library", "Application Support", "Kodi", "userdata"]
];

/**
 * Returns the path to Kodi's video playlists folder if it exists; aborts the
 * entire program if it doesn't. The path is discovered by checking for the
 * default path on various platforms, so if the user has installed Kodi in a
 * non-standard location, this can fail.
 *
 * TODO: Allow the user to manually specify where they installed Kodi without
 * having to edit `kodilist.json`.
 */
export function getKodiFolder(): string {
  const home = os.homedir();
  const kodiFolder = possiblePlaylistFolders.find(folder => {
    const absolutePath = path.resolve(path.join(home, ...folder));
    return fs.existsSync(absolutePath);
  });
  if (kodiFolder === undefined) {
    console.error("Could not locate Kodi folder!");
    return process.exit(1);
  }
  return path.resolve(path.join(home, ...kodiFolder));
}

/**
 * Creates a folder at the supplied path if it doesn't already exist.
 */
export function guaranteeFolder(root: string, name: string): void {
  const target = path.join(root, name);
  if (!fs.existsSync(target)) fs.mkdirSync(target);
}

/**
 * Writes M3U playlists for each of the user's tracked people, tags, and
 * sources in folders named 'people', 'tags', and 'sources' respectively.
 */
export function writePlaylistsSearchTerms(
  config: ConfigFile,
  files: any[]
): void {
  Object.keys(config.searchTerms).forEach((folderName: string) => {
    process.stdout.write("  " + folderName + "...");
    guaranteeFolder(config.kodi, folderName);
    config.searchTerms[folderName].forEach((tagName: string) => {
      const matches = files
        .filter(name => name.simpleName.includes(tagName))
        .map(file => file.filename)
        .join("\n");
      const p = path.join(config.kodi, folderName, tagName + ".m3u");
      fs.writeFileSync(p, matches);
    });
    process.stdout.write(`\r${chalk.green("✔️")} ${folderName}            \n`);
  });
}

/**
 * Writes a playlist `new.m3u` with files created within the last week,
 * measured using `fs.Stats.birthTimeMs`. For large libraries this is the
 * slowest part of the program after generating the initial file list. This
 * could be improved performance-wise by caching data from the last run, at the
 * potential cost of reliability -- what if a file is replaced by a new but
 * identically-named file -- and we'd still need to check that the files still
 * exist so it'd still involve touching the disk again.
 */
export function writePlaylistsNew(config: ConfigFile, files: any[]): void {
  process.stdout.write("  this week's files...");
  const now = new Date().getTime();
  const lastWeek = now - 604800000;
  const newFiles = files
    .filter(file => file.mtime > lastWeek)
    .sort((a: any, b: any) => (a.mtime > b.mtime ? -1 : 1))
    .map(file => file.filename)
    .join("\n");
  fs.writeFileSync(path.join(config.kodi, "new.m3u"), newFiles);
  process.stdout.write(`\r${chalk.green("✔️")} this week's files          \n`);
}

const alphabet: { [index: string]: string[] } = {
  a: [],
  b: [],
  c: [],
  d: [],
  e: [],
  f: [],
  g: [],
  h: [],
  i: [],
  j: [],
  k: [],
  l: [],
  m: [],
  n: [],
  o: [],
  p: [],
  q: [],
  r: [],
  s: [],
  t: [],
  u: [],
  v: [],
  w: [],
  x: [],
  y: [],
  z: [],
  "!": []
};

/**
 * Writes a playlist `a.m3u` with all files beginning with A or a, `b.m3u`
 * with all files beginning with B or b, and so on. If the filename begins with
 * any non-alphabetical character it falls under `!.m3u`.
 */
export function writePlaylistsAlphabet(config: ConfigFile, files: any[]): void {
  guaranteeFolder(config.kodi, "alphabetical");
  process.stdout.write("  alphabetical files...");
  files.forEach(file => {
    if (alphabet.hasOwnProperty(file.simpleName[0])) {
      alphabet[file.simpleName[0]].push(file.filename);
    } else {
      alphabet["!"].push(file.filename);
    }
  });
  Object.keys(alphabet).forEach(letter => {
    fs.writeFileSync(
      path.join(config.kodi, "alphabetical", letter + ".m3u"),
      alphabet[letter].join("\n")
    );
  });
  process.stdout.write(`\r${chalk.green("✔️")} alphabetical files          \n`);
}
