import * as path from 'path';
import * as fs from 'fs';
import { WorkspaceFolder, Position, Range, TextEditor } from 'vscode';
import { EOL } from 'os';
import { CsvEntry } from '../model';

/**
 * remove a trailing slash from a string when exists
 * @param s the input string
 */
export const removeTrailingSlash = (s: string): string => s.replace(/\/$|\\$/, '');

/**
 * remove a leading slash from a string when exists
 * @param s the input string
 */
export const removeLeadingSlash = (s: string): string => s.replace(/^\/|^\\/, '');

/**
 * remove leading and trailing slash from a string when exists
 * @param s the input string
 */
export const removeLeadingAndTrailingSlash = (s: string): string => removeLeadingSlash(removeTrailingSlash(s));

/**
 * Check is `dir` is a proper subpath of `base`.
 * @param dir Directory to check if being a subpath of `base`.
 * @param base Base directory.
 * @returns Whether `dir` is a proper subpath of `base`.
 */
export const isProperSubpathOf = (dir: string, base: string): boolean => {
  const relative = path.relative(base, dir);
  return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);
};

/**
 * Get the path name of the workspace
 * workspace root, assumed to be the first item in the array
 * @param folders the workspace folder object from vscode (via: `vscode.workspace.workspaceFolders`)
 */
export const getWorkspaceFolder = (folders: WorkspaceFolder[] | undefined, activeTextEditor?: TextEditor): string => {
  if (!folders || !folders[0] || !folders[0].uri || !folders[0].uri.fsPath) {
    // Edge-Case (See Issue #108): Handle the case we are not actually in an workspace but a single file has been picked for review in VSCode
    // In this case, the review file will be stored next to this file in the same directory
    const currentFile = activeTextEditor?.document.fileName;
    return currentFile ? path.dirname(currentFile) : '';
  }
  return folders[0].uri.fsPath;
};

/**
 * takes the workspace root and a filename or relative path and returns an absolute path
 * @param workspaceRoot the the workspace path
 * @param filename the name of the file
 */
export const toAbsolutePath = (workspaceRoot: string, filename: string): string => {
  const harmonizedFileName = filename.replace(/\\/g, '/');
  return path.resolve(workspaceRoot, removeLeadingSlash(harmonizedFileName));
};

/**
 * Get the content of a file for a defined line range
 * @param pathToFile the actual file path and name
 * @param range the selection range
 */
export const getFileContentForRange = (pathToFile: string, range: Range): string => {
  let fileContent = '';
  try {
    fileContent = fs.readFileSync(pathToFile, 'utf8');
  } catch (error) {
    console.log('Error reading file', pathToFile, error);
  }
  const fileContentLines = fileContent.split(EOL);
  return fileContentLines.slice(range.start.line, range.end.line).join(EOL);
};

/**
 * Double quotes must be escaped in CSV files using another leading double quote
 * @param input the string that should be escaped
 */
export const escapeDoubleQuotesForCsv = (input: string): string => {
  return input.replace(/"/g, '""');
};

/**
 * End-of-line must be escaped in CSV files to prevent row discontinuity
 * @param input the string that should be escaped
 */
export const escapeEndOfLineForCsv = (input: string): string => {
  return input.replace(/\n/g, '\\n');
};

/**
 * Restore escaped end-of-line for manipulation
 * @param input the string that should be unescaped
 */
export const unescapeEndOfLineFromCsv = (input: string): string => {
  return input.replace(/\\n/g, '\n');
};

/**
 * Retrieve the first start line definition from the lines string representation for CSV files
 * @param input the input string
 */
export const startLineNumberFromStringDefinition = (input: string): number => {
  const matches = input.match(/^\d+/s);
  return matches ? Number(matches[0]) : 0;
};

/**
 * Retrieve the first start line position definition from the lines string representation for CSV files
 * @param input the input string
 */
export const startPositionNumberFromStringDefinition = (input: string): number => {
  const matches = input.match(/(?<=:)\d+/s);
  return matches ? Number(matches[0]) : 0;
};

/**
 * Retrieve the first end line definition from the lines string representation for CSV files
 * @param input the input string
 */
export const endLineNumberFromStringDefinition = (input: string): number => {
  const matches = input.match(/(?<=-)\d+/s);
  return matches ? Number(matches[0]) : 0;
};

/**
 * Retrieve the first end line position definition from the lines string representation for CSV files
 * @param input the input string
 */
export const endPositionNumberFromStringDefinition = (input: string): number => {
  const matches = input.match(/(?<=:)\d+/g);
  return matches && matches[1] ? Number(matches[1]) : 0;
};

/**
 * Get the range for the lines string representation for CSV files
 * @param input the input string
 */
export const rangeFromStringDefinition = (input: string, offset: number = 0): Range => {
  // Position expects a zero-based index, but line numbers in the csv it saved as one-based index
  const startLine = startLineNumberFromStringDefinition(input) - 1;
  const startPosition = startPositionNumberFromStringDefinition(input);
  const endLine = endLineNumberFromStringDefinition(input) - 1 + offset;
  const endPosition = endPositionNumberFromStringDefinition(input);

  return new Range(
    // fall back to 0 when value is lower than 0
    new Position(startLine > 0 ? startLine : 0, startPosition > 0 ? startPosition : 0),
    new Position(endLine > 0 ? endLine : 0, endPosition > 0 ? endPosition : 0),
  );
};

/**
 * Get the ranges for the lines string representation for CSV files
 * @param input the input string (can have multiple blocks, joined by '|' character)
 */
export const rangesFromStringDefinition = (input: string, offset: number = 0): Range[] => {
  return splitStringDefinition(input).map((str) => rangeFromStringDefinition(str, offset));
};

/**
 * split strings like `0:12-15:14|2:34-19:23` by `|` character
 * @param input the raw string definition
 */
export const splitStringDefinition = (input: string): string[] => input.split('|').map((str) => str.trim());

/**
 * Sort function to order the lines string representation for CSV files
 * @param localA compare value a
 * @param localB compare value b
 */
export const sortLineSelections = (localA: string, localB: string): number =>
  startLineNumberFromStringDefinition(localA) - startLineNumberFromStringDefinition(localB);

/**
 * Sort CSV entries for their lines and
 * @param a compare CSV definition A
 * @param b compare CSV definition B
 * @see https://github.com/d-koppenhagen/vscode-code-review/issues/38
 */
export const sortCsvEntryForLines = (a: CsvEntry, b: CsvEntry): number => {
  const aSplit = splitStringDefinition(a.lines);
  const bSplit = splitStringDefinition(b.lines);

  aSplit.sort(sortLineSelections);
  bSplit.sort(sortLineSelections);

  const lineASplitStart = startLineNumberFromStringDefinition(aSplit[0]);
  const lineASplitEnd = endLineNumberFromStringDefinition(aSplit[0]);
  const lineBSplitStart = startLineNumberFromStringDefinition(bSplit[0]);
  const lineBSplitEnd = endLineNumberFromStringDefinition(bSplit[0]);

  // handle the case that the range is bigger compared to the second range.
  // e.g. 4:5-15:0 VS. 4:2-6:7
  // both ranges starting at line 4. But the first range is bigger (4-15) compared to the second one (4-6)
  if (lineASplitStart <= lineBSplitStart && lineASplitEnd >= lineBSplitEnd) {
    return -1;
  }

  return lineASplitStart - lineBSplitStart;
};

/**
 * Generate a backup file name
 *
 * @param reviewFilePath The full name of the file to backup
 * @return The name of the backup file
 */
export const getBackupFilename = (reviewFilePath: string): string => {
  const timeStamp = new Date().toISOString().replace(/[:,\.]/g, '-');
  return path.join(path.dirname(reviewFilePath), path.parse(reviewFilePath).name + '-' + timeStamp + '.bak');
};

/**
 * Refine a file name
 * @param workspaceRoot The root path of the workspace
 * @param filename The name of the file
 */
export const standardizeFilename = (workspaceRoot: string, filename: string): string => {
  return filename.replace(workspaceRoot, '');
};