<commit_before>/*
 * Copyright 2018 Crown Copyright
 *
 * Licensed under the Apache 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
 *
 *     http://www.apache.org/licenses/LICENSE-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 { findItem, itemIsInSubtree, iterateNodes } from "../../lib/treeUtils";
import {
  PipelineModelType,
  PipelineAsTreeType,
  PipelineElementType,
  ElementDefinitionsByType,
  ElementDefinition,
  PipelineDataType,
  PipelinePropertyType,
  AddRemove,
  PipelineLinkType,
  ElementPropertiesType
} from "../../types";

export interface RecycleBinItem {
  recycleData?: PipelineElementType;
  element: ElementDefinition;
}

export function getBinItems(
  pipeline: PipelineModelType,
  elementsByType: ElementDefinitionsByType
): Array<RecycleBinItem> {
  const thisConfigStack = pipeline.configStack[pipeline.configStack.length - 1];

  return thisConfigStack.elements.remove
    ? thisConfigStack.elements.remove.map(e => ({
        recycleData: e,
        element: elementsByType[e.type]
      }))
    : [];
}

/**
 * This function takes the denormalised pipeline definition and creates
 * a tree like structure. The root element is the 'from' of the first link.
 * This tree structure can then be used to lay the elements out in the graphical display.
 *
 * @param {object} pipeline
 * @return {object} Tree like structure
 */
export function getPipelineAsTree(
  pipeline: PipelineModelType
): PipelineAsTreeType | undefined {
  const elements: {
    [id: string]: PipelineAsTreeType;
  } = {};

  // Put all the elements into an object, keyed on id
  if (pipeline.merged.elements.add) {
    pipeline.merged.elements.add.forEach(e => {
      elements[e.id] = {
        uuid: e.id,
        type: e.type,
        children: []
      };
    });
  }

  // Create the tree using links
  if (pipeline.merged.links.add) {
    pipeline.merged.links.add.filter(l => !!elements[l.from]).forEach(l => {
      elements[l.from].children.push(elements[l.to]);
    });
  }

  // Figure out the root
  let rootId;

  // First, is there an element of type Source?
  if (elements.Source) {
    rootId = "Source";
  } else {
    // if a link doesn't have anything going to it then it may be a root.
    const rootLinks = pipeline.merged.links.add
      ? pipeline.merged.links.add.filter(fromLink => {
          const toLinks = pipeline.merged.links.add!.filter(
            l => fromLink.from === l.to
          );
          return toLinks.length === 0;
        })
      : [];

    if (rootLinks.length === 0) {
      // Maybe there's only one thing and therefore no links?
      if (
        pipeline.merged.elements.add &&
        pipeline.merged.elements.add.length !== 0
      ) {
        // If there're no links then we can use the first element.
        rootId = pipeline.merged.elements.add[0].id;
      } else {
        // If there are no elements then we can't have a root node.
        rootId = undefined;
      }
    } else {
      // Just pick the first potential root? (not sure about this)
      rootId = rootLinks[0].from;
    }
  }

  return rootId ? elements[rootId] : undefined;
}

export interface PipelineLayoutInfo {
  column: number;
  row: number;
}

export interface PipelineLayoutInfoById {
  [uuid: string]: PipelineLayoutInfo;
}

export enum CellType {
  EMPTY,
  ELBOW,
  ELEMENT
}

export interface PipelineLayoutCell {
  cellType: CellType;
  uuid?: string;
}

export interface PipelineLayoutRow {
  columns: Array<PipelineLayoutCell>;
}

export interface PipelineLayoutGrid {
  rows: Array<PipelineLayoutRow>;
}

/**
 * This calculates the layout information for the elements in a tree where the tree
 * must be layed out in a graphical manner.
 * The result object only indicates horizontal and vertical positions as 1-up integers.
 * A further mapping is required to convert this to specific layout pixel information.
 *
 * @param {treeNode} asTree The pipeline information as a tree with UUID's and 'children' on nodes.
 * @return {object} An object with a key for each UUID in the tree. The values are objects with
 * the following properties {horizontalPos, verticalPos}. These position indicators are just
 * 1-up integer values that can then bo converted to specific layout information (pixel position, position in grid etc)
 */
export function getPipelineLayoutGrid(
  asTree: PipelineAsTreeType
): PipelineLayoutGrid {
  const layoutInformation: PipelineLayoutInfoById = {};

  let highestColumn = 0;
  let lastRow = 0;
  let lastColumn = -1;
  iterateNodes(asTree, (lineage, node) => {
    const column = lineage.length;

    if (column <= lastColumn) {
      // This means you have taken a sideways step to the next child (at some any level)
      lastRow += 1;
    }
    highestColumn = Math.max(highestColumn, column);
    lastColumn = column;

    layoutInformation[node.uuid] = {
      row: lastRow,
      column
    };
  });

  const layoutGrid: PipelineLayoutGrid = {
    rows: []
  };
  for (let row = 0; row <= lastRow; row++) {
    let rowData: PipelineLayoutRow = {
      columns: []
    };
    for (let column = 0; column <= lastColumn; column++) {
      rowData.columns.push({
        cellType: CellType.EMPTY
      });
    }
    layoutGrid.rows.push(rowData);
  }

  Object.entries(layoutInformation)
    .map(k => ({
      uuid: k[0],
      layoutInfo: k[1]
    }))
    .forEach(({ uuid, layoutInfo: { row, column } }) => {
      if (column > 0) {
        if (
          layoutGrid.rows[row].columns[column - 1].cellType == CellType.EMPTY
        ) {
          layoutGrid.rows[row].columns[column - 1].cellType = CellType.ELBOW;
        }
      }
      layoutGrid.rows[row].columns[column].cellType = CellType.ELEMENT;
      layoutGrid.rows[row].columns[column].uuid = uuid;
    });

  return layoutGrid;
}

/**
 * Use this function to retrieve all element names from the pipeline.
 *
 * @param {pipeline} pipeline The pipeline from with to extract names
 */
export function getAllElementNames(pipeline: PipelineModelType): Array<string> {
  const names: Array<string> = [];

  pipeline.merged.elements.add &&
    pipeline.merged.elements.add
      .map(e => e.id)
      .map(id => id.toLowerCase())
      .forEach(id => names.push(id));
  pipeline.configStack.forEach(cs => {
    cs.elements.add &&
      cs.elements.add
        .map(e => e.id)
        .map(id => id.toLowerCase())
        .forEach(id => names.push(id));
  });

  return names;
}

/**
 * Utility function for replacing the last item in an array by using a given map function.
 * All the other items are left as they are.
 */
function mapLastItemInArray(input: any, mapFunc: (input: any) => any) {
  return input.map(
    (item: any, i: number, arr: Array<any>) =>
      i === arr.length - 1 ? mapFunc(item) : item
  );
}

/**
 * Adds a new entry to a pipeline for the chosen element definition.
 * Creates the link between the new element and the given parent.
 * The new element will be given the name as it's unique ID.
 *
 * @param {pipeline} pipeline The current total picture of the pipeline
 * @param {string} parentId The ID of the parent that the link is being added to.
 * @param {element} childDefinition The definiton of the new element.
 * @param {string} name The name to give to the new element.
 */
export function createNewElementInPipeline(
  pipeline: PipelineModelType,
  parentId: string,
  childDefinition: ElementDefinition,
  name: string
): PipelineModelType {
  const newElement = {
    id: name,
    type: childDefinition.type
  };
  const newLink = {
    from: parentId,
    to: name<commit_msg>fixed the crashing bug when new elements added<commit_after>/*
 * Copyright 2018 Crown Copyright
 *
 * Licensed under the Apache 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
 *
 *     http://www.apache.org/licenses/LICENSE-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 { findItem, itemIsInSubtree, iterateNodes } from "../../lib/treeUtils";
import {
  PipelineModelType,
  PipelineAsTreeType,
  PipelineElementType,
  ElementDefinitionsByType,
  ElementDefinition,
  PipelineDataType,
  PipelinePropertyType,
  AddRemove,
  PipelineLinkType,
  ElementPropertiesType
} from "../../types";

export interface RecycleBinItem {
  recycleData?: PipelineElementType;
  element: ElementDefinition;
}

export function getBinItems(
  pipeline: PipelineModelType,
  elementsByType: ElementDefinitionsByType
): Array<RecycleBinItem> {
  const thisConfigStack = pipeline.configStack[pipeline.configStack.length - 1];

  return thisConfigStack.elements.remove
    ? thisConfigStack.elements.remove.map(e => ({
        recycleData: e,
        element: elementsByType[e.type]
      }))
    : [];
}

/**
 * This function takes the denormalised pipeline definition and creates
 * a tree like structure. The root element is the 'from' of the first link.
 * This tree structure can then be used to lay the elements out in the graphical display.
 *
 * @param {object} pipeline
 * @return {object} Tree like structure
 */
export function getPipelineAsTree(
  pipeline: PipelineModelType
): PipelineAsTreeType | undefined {
  const elements: {
    [id: string]: PipelineAsTreeType;
  } = {};

  // Put all the elements into an object, keyed on id
  if (pipeline.merged.elements.add) {
    pipeline.merged.elements.add.forEach(e => {
      elements[e.id] = {
        uuid: e.id,
        type: e.type,
        children: []
      };
    });
  }

  // Create the tree using links
  if (pipeline.merged.links.add) {
    pipeline.merged.links.add
      .filter(l => !!elements[l.from])
      .forEach(l => {
        elements[l.from].children.push(elements[l.to]);
      });
  }

  // Figure out the root
  let rootId;

  // First, is there an element of type Source?
  if (elements.Source) {
    rootId = "Source";
  } else {
    // if a link doesn't have anything going to it then it may be a root.
    const rootLinks = pipeline.merged.links.add
      ? pipeline.merged.links.add.filter(fromLink => {
          const toLinks = pipeline.merged.links.add!.filter(
            l => fromLink.from === l.to
          );
          return toLinks.length === 0;
        })
      : [];

    if (rootLinks.length === 0) {
      // Maybe there's only one thing and therefore no links?
      if (
        pipeline.merged.elements.add &&
        pipeline.merged.elements.add.length !== 0
      ) {
        // If there're no links then we can use the first element.
        rootId = pipeline.merged.elements.add[0].id;
      } else {
        // If there are no elements then we can't have a root node.
        rootId = undefined;
      }
    } else {
      // Just pick the first potential root? (not sure about this)
      rootId = rootLinks[0].from;
    }
  }

  return rootId ? elements[rootId] : undefined;
}

export interface PipelineLayoutInfo {
  column: number;
  row: number;
}

export interface PipelineLayoutInfoById {
  [uuid: string]: PipelineLayoutInfo;
}

export enum CellType {
  EMPTY,
  ELBOW,
  ELEMENT
}

export interface PipelineLayoutCell {
  cellType: CellType;
  uuid?: string;
}

export interface PipelineLayoutRow {
  columns: Array<PipelineLayoutCell>;
}

export interface PipelineLayoutGrid {
  rows: Array<PipelineLayoutRow>;
}

/**
 * This calculates the layout information for the elements in a tree where the tree
 * must be layed out in a graphical manner.
 * The result object only indicates horizontal and vertical positions as 1-up integers.
 * A further mapping is required to convert this to specific layout pixel information.
 *
 * @param {treeNode} asTree The pipeline information as a tree with UUID's and 'children' on nodes.
 * @return {object} An object with a key for each UUID in the tree. The values are objects with
 * the following properties {horizontalPos, verticalPos}. These position indicators are just
 * 1-up integer values that can then bo converted to specific layout information (pixel position, position in grid etc)
 */
export function getPipelineLayoutGrid(
  asTree: PipelineAsTreeType
): PipelineLayoutGrid {
  const layoutInformation: PipelineLayoutInfoById = {};

  let highestColumn = 0;
  let highestRow = 0;
  let lastColumn = -1;
  iterateNodes(asTree, (lineage, node) => {
    const column = lineage.length;

    if (column <= lastColumn) {
      // This means you have taken a sideways step to the next child (at some any level)
      highestRow += 1;
    }
    highestColumn = Math.max(highestColumn, column);
    lastColumn = column;

    layoutInformation[node.uuid] = {
      row: highestRow,
      column
    };
  });

  const layoutGrid: PipelineLayoutGrid = {
    rows: []
  };
  for (let row = 0; row <= highestRow; row++) {
    let rowData: PipelineLayoutRow = {
      columns: []
    };
    for (let column = 0; column <= highestColumn; column++) {
      rowData.columns.push({
        cellType: CellType.EMPTY
      });
    }
    layoutGrid.rows.push(rowData);
  }

  Object.entries(layoutInformation)
    .map(k => ({
      uuid: k[0],
      layoutInfo: k[1]
    }))
    .forEach(({ uuid, layoutInfo: { row, column } }) => {
      if (column > 0) {
        if (
          layoutGrid.rows[row].columns[column - 1].cellType == CellType.EMPTY
        ) {
          layoutGrid.rows[row].columns[column - 1].cellType = CellType.ELBOW;
        }
      }
      layoutGrid.rows[row].columns[column].cellType = CellType.ELEMENT;
      layoutGrid.rows[row].columns[column].uuid = uuid;
    });

  return layoutGrid;
}

/**
 * Use this function to retrieve all element names from the pipeline.
 *
 * @param {pipeline} pipeline The pipeline from with to extract names
 */
export function getAllElementNames(pipeline: PipelineModelType): Array<string> {
  const names: Array<string> = [];

  pipeline.merged.elements.add &&
    pipeline.merged.elements.add
      .map(e => e.id)
      .map(id => id.toLowerCase())
      .forEach(id => names.push(id));
  pipeline.configStack.forEach(cs => {
    cs.elements.add &&
      cs.elements.add
        .map(e => e.id)
        .map(id => id.toLowerCase())
        .forEach(id => names.push(id));
  });

  return names;
}

/**
 * Utility function for replacing the last item in an array by using a given map function.
 * All the other items are left as they are.
 */
function mapLastItemInArray(input: any, mapFunc: (input: any) => any) {
  return input.map((item: any, i: number, arr: Array<any>) =>
    i === arr.length - 1 ? mapFunc(item) : item
  );
}

/**
 * Adds a new entry to a pipeline for the chosen element definition.
 * Creates the link between the new element and the given parent.
 * The new element will be given the name as it's unique ID.
 *
 * @param {pipeline} pipeline The current total picture of the pipeline
 * @param {string} parentId The ID of the parent that the link is being added to.
 * @param {element} childDefinition The definiton of the new element.
 * @param {string} name The name to give to the new element.
 */
export function createNewElementInPipeline(
  pipeline: PipelineModelType,
  parentId: string,
  childDefinition: ElementDefinition,
  name: string
): PipelineModelType {
  const newElement = {
    id: name,
    type: childDefinition.type
  };
  const newLink = {
    from: parentId,
    to: name
{'fraction_non_alphanumeric': 0.1058433018099831, 'fraction_numerical': 0.0035072336694432268, 'mean_word_length': 3.0231796422272614, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '134039', 'n_tokens_mistral': 4689, 'n_tokens_neox': 4587, 'n_words': 1551}