/*jshint globalstrict:true, strict:true, esnext: true */

"use strict";

// //////////////////////////////////////////////////////////////////////////////
// / DISCLAIMER
// /
// / Copyright 2014-2024 ArangoDB GmbH, Cologne, Germany
// /
// / The Programs (which include both the software and documentation) contain
// / proprietary information of ArangoDB GmbH; they are provided under a license
// / agreement containing restrictions on use and disclosure and are also
// / protected by copyright, patent and other intellectual and industrial
// / property laws. Reverse engineering, disassembly or decompilation of the
// / Programs, except to the extent required to obtain interoperability with
// / other independently created software or as specified by law, is prohibited.
// /
// / It shall be the licensee's responsibility to take all appropriate fail-safe,
// / backup, redundancy, and other measures to ensure the safe use of
// / applications if the Programs are used for purposes such as nuclear,
// / aviation, mass transit, medical, or other inherently dangerous applications,
// / and ArangoDB GmbH disclaims liability for any damages caused by such use of
// / the Programs.
// /
// / This software is the confidential and proprietary information of ArangoDB
// / GmbH. You shall not disclose such confidential and proprietary information
// / and shall use it only in accordance with the terms of the license agreement
// / you entered into with ArangoDB GmbH.
// /
// / Copyright holder is ArangoDB GmbH, Cologne, Germany
// /
/// @author Michael Hackstein
// //////////////////////////////////////////////////////////////////////////////

const {db} = require("@arangodb");
const sgm = require("@arangodb/smart-graph");
const {CollectionWrapper} = require("@arangodb/testutils/collection-wrapper-util");

const smartGraphName = "UnitTestHybridSmartGraph";
const satelliteCollectionName = "UnitTestSmartSat";
const verticesCollectionName = "UnitTestSmartVertices";
const edgesCollectionName = "UnitTestSmartEdges";
const edgesSatToSatCollectionName = "UnitTestSatToSatEdges";
const edgesSatToSmartCollectionName = "UnitTestSatToSmartEdges";
const edgesSmartToSatCollectionName = "UnitTestSmartToSatEdges";
const smartGraphAttribute = "smart";
// TODO: Maybe we want to add crazy things like Objects, arrays, numbers, null, undefined here
const validSmartGraphValues = ["B", "a", "C", "LongerName"];

const makeOptions = (isDisjoint, isEnterpriseGraph) => {
  let options = {
    satellites: [satelliteCollectionName],
    isDisjoint,
    isSmart: true,
    numberOfShards: 6,
    replicationFactor: 1
  };
  if (!isEnterpriseGraph) {
    options.smartGraphAttribute = smartGraphAttribute;
  }
  return options;
};

const createHybridSmartGraph = (isDisjoint, isEnterpriseGraph) => {
  let options = makeOptions(isDisjoint, isEnterpriseGraph);
  const edgeDefinitions = [
    sgm._relation(
      edgesCollectionName,
      verticesCollectionName,
      verticesCollectionName
    ),
    sgm._relation(
      edgesSatToSatCollectionName,
      satelliteCollectionName,
      satelliteCollectionName
    ),
    sgm._relation(
      edgesSmartToSatCollectionName,
      verticesCollectionName,
      satelliteCollectionName
    ),
    sgm._relation(
      edgesSatToSmartCollectionName,
      satelliteCollectionName,
      verticesCollectionName
    )
  ];
  sgm._create(smartGraphName, edgeDefinitions, [], options);
};

const dropSmartGraph = () => {
  try {
    sgm._drop(smartGraphName, true);
  } catch (_) {
  }
};

class HybridSmartGraphSatelliteCollectionWrapper extends CollectionWrapper {
  constructor(isDisjoint, isEnterpriseGraph) {
    super(satelliteCollectionName);
    this._isDisjoint = isDisjoint;
    this._isEnterpriseGraph = isEnterpriseGraph;
  }

  setUp() {
    createHybridSmartGraph(this._isDisjoint, this._isEnterpriseGraph);
  }

  tearDown() {
    dropSmartGraph();
  }
}

function* SmartVertexGenerator(keyGenerator, isEnterpriseGraph) {
  for (const key of keyGenerator) {
    for (const smartValue of validSmartGraphValues) {
      if (isEnterpriseGraph) {
        /*
         * Just mimic the original SmartVertexKey generator
         * Any regular ArangoDB collection key is valid, we do not
         * have SmartKey regulations in case of Enterprise Graph.
         */
        yield {
          _key: `${smartValue}${key}`,
          value: "test"
        };
      } else {
        yield {
          _key: `${smartValue}:${key}`,
          value: "test",
          [smartGraphAttribute]: smartValue
        };
      }
    }
  }
}

class HybridSmartGraphSmartCollectionWrapper extends CollectionWrapper {
  constructor(isDisjoint, isEnterpriseGraph) {
    super(verticesCollectionName);
    this._isDisjoint = isDisjoint;
    this._isEnterpriseGraph = isEnterpriseGraph;
  }

  setUp() {
    createHybridSmartGraph(this._isDisjoint, this._isEnterpriseGraph);
  }

  tearDown() {
    dropSmartGraph();
  }

  documentGeneratorWithKeys(keyGenerator) {
    return SmartVertexGenerator(keyGenerator, this._isEnterpriseGraph);
  }
}

function* SmartEdgeGenerator(keyGenerator, fromIsSmart, toIsSmart, isDisjoint, isEnterpriseGraph) {
  for (const key of keyGenerator) {
    if (fromIsSmart) {
      for (const fromSmartValue of validSmartGraphValues) {
        let _from;
        if (isEnterpriseGraph) {
          _from = `${verticesCollectionName}/test`;
        } else {
          _from = `${verticesCollectionName}/${fromSmartValue}:test`;
        }

        if (toIsSmart) {
          for (const toSmartValue of validSmartGraphValues) {
            if (isDisjoint && fromSmartValue !== toSmartValue) {
              // In the Disjoint case we can only allow _from and _to to be
              // Sharded identically.
              continue;
            }
            let _to;
            if (isEnterpriseGraph) {
              _to = `${verticesCollectionName}/test`;
            } else {
              _to = `${verticesCollectionName}/${toSmartValue}:test`;
            }

            if (isEnterpriseGraph) {
              yield {
                value: "test",
                _from,
                _to
              };
            } else {
              yield {
                _key: `${fromSmartValue}:${key}:${toSmartValue}`,
                value: "test",
                _from,
                _to
              };
            }
          }
        } else {
          const _to = `${satelliteCollectionName}/test`;
          if (isEnterpriseGraph) {
            yield {
              value: "test",
              _from,
              _to
            };
          } else {
            yield {
              _key: `${fromSmartValue}:${key}`,
              value: "test",
              _from,
              _to
            };
          }
        }
      }
    } else {
      const _from = `${satelliteCollectionName}/test`;
      if (toIsSmart) {
        for (const toSmartValue of validSmartGraphValues) {
          let _to;
          if (isEnterpriseGraph) {
            _to = `${verticesCollectionName}/test`;
          } else {
            _to = `${verticesCollectionName}/${toSmartValue}:test`;
          }

          if (isEnterpriseGraph) {
            yield {
              value: "test",
              _from,
              _to
            };
          } else {
            yield {
              _key: `${key}:${toSmartValue}`,
              value: "test",
              _from,
              _to
            };
          }
        }
      } else {
        const _to = `${satelliteCollectionName}/test`;
        yield {
          _key: `${key}`,
          value: "test",
          _from,
          _to
        };
      }
    }
  }
}

class HybridSmartGraphSmartEdgeCollectionWrapper extends CollectionWrapper {
  constructor(isDisjoint, isEnterpriseGraph, fromIsSmart, toIsSmart) {
    if (fromIsSmart) {
      if (toIsSmart) {
        super(edgesCollectionName);
      } else {
        super(edgesSmartToSatCollectionName);
      }
    } else {
      if (toIsSmart) {
        super(edgesSatToSmartCollectionName);
      } else {
        super(edgesSatToSatCollectionName);
      }
    }
    this._isDisjoint = isDisjoint;
    this._isEnterpriseGraph = isEnterpriseGraph;
    this._isEnterpriseGraphEdge = true;
    this._fromIsSmart = fromIsSmart;
    this._toIsSmart = toIsSmart;
  }

  setUp() {
    createHybridSmartGraph(this._isDisjoint, this._isEnterpriseGraph);
  }

  tearDown() {
    dropSmartGraph();
  }

  documentGeneratorWithKeys(keyGenerator) {
    return SmartEdgeGenerator(
      keyGenerator, this._fromIsSmart, this._toIsSmart, this._isDisjoint, this._isEnterpriseGraph
    );
  }
}

exports.HybridSmartGraphSatelliteCollectionWrapper = HybridSmartGraphSatelliteCollectionWrapper;
exports.HybridSmartGraphSmartCollectionWrapper = HybridSmartGraphSmartCollectionWrapper;
exports.HybridSmartGraphSmartEdgeCollectionWrapper = HybridSmartGraphSmartEdgeCollectionWrapper;