const TestObject = Parse.Object.extend('TestObject');
const request = require('../lib/request');
const TestUtils = require('../lib/TestUtils');
const defaultHeaders = {
  'X-Parse-Application-Id': 'test',
  'X-Parse-Rest-API-Key': 'rest',
  'Content-Type': 'application/json',
};

describe('Parse.Polygon testing', () => {
  it('polygon save open path', done => {
    const coords = [
      [0, 0],
      [0, 1],
      [1, 1],
      [1, 0],
    ];
    const closed = [
      [0, 0],
      [0, 1],
      [1, 1],
      [1, 0],
      [0, 0],
    ];
    const obj = new TestObject();
    obj.set('polygon', new Parse.Polygon(coords));
    return obj
      .save()
      .then(() => {
        const query = new Parse.Query(TestObject);
        return query.get(obj.id);
      })
      .then(result => {
        const polygon = result.get('polygon');
        equal(polygon instanceof Parse.Polygon, true);
        equal(polygon.coordinates, closed);
        done();
      }, done.fail);
  });

  it('polygon save closed path', done => {
    const coords = [
      [0, 0],
      [0, 1],
      [1, 1],
      [1, 0],
      [0, 0],
    ];
    const obj = new TestObject();
    obj.set('polygon', new Parse.Polygon(coords));
    return obj
      .save()
      .then(() => {
        const query = new Parse.Query(TestObject);
        return query.get(obj.id);
      })
      .then(result => {
        const polygon = result.get('polygon');
        equal(polygon instanceof Parse.Polygon, true);
        equal(polygon.coordinates, coords);
        done();
      }, done.fail);
  });

  it_id('3019353b-d5b3-4e53-bcb1-716418328bdd')(it)('polygon equalTo (open/closed) path', done => {
    const openPoints = [
      [0, 0],
      [0, 1],
      [1, 1],
      [1, 0],
    ];
    const closedPoints = [
      [0, 0],
      [0, 1],
      [1, 1],
      [1, 0],
      [0, 0],
    ];
    const openPolygon = new Parse.Polygon(openPoints);
    const closedPolygon = new Parse.Polygon(closedPoints);
    const obj = new TestObject();
    obj.set('polygon', openPolygon);
    return obj
      .save()
      .then(() => {
        const query = new Parse.Query(TestObject);
        query.equalTo('polygon', openPolygon);
        return query.find();
      })
      .then(results => {
        const polygon = results[0].get('polygon');
        equal(polygon instanceof Parse.Polygon, true);
        equal(polygon.coordinates, closedPoints);
        const query = new Parse.Query(TestObject);
        query.equalTo('polygon', closedPolygon);
        return query.find();
      })
      .then(results => {
        const polygon = results[0].get('polygon');
        equal(polygon instanceof Parse.Polygon, true);
        equal(polygon.coordinates, closedPoints);
        done();
      }, done.fail);
  });

  it('polygon update', done => {
    const oldCoords = [
      [0, 0],
      [0, 1],
      [1, 1],
      [1, 0],
    ];
    const oldPolygon = new Parse.Polygon(oldCoords);
    const newCoords = [
      [2, 2],
      [2, 3],
      [3, 3],
      [3, 2],
    ];
    const newPolygon = new Parse.Polygon(newCoords);
    const obj = new TestObject();
    obj.set('polygon', oldPolygon);
    return obj
      .save()
      .then(() => {
        obj.set('polygon', newPolygon);
        return obj.save();
      })
      .then(() => {
        const query = new Parse.Query(TestObject);
        return query.get(obj.id);
      })
      .then(result => {
        const polygon = result.get('polygon');
        newCoords.push(newCoords[0]);
        equal(polygon instanceof Parse.Polygon, true);
        equal(polygon.coordinates, newCoords);
        done();
      }, done.fail);
  });

  it('polygon invalid value', done => {
    const coords = [
      ['foo', 'bar'],
      [0, 1],
      [1, 0],
      [1, 1],
      [0, 0],
    ];
    const obj = new TestObject();
    obj.set('polygon', { __type: 'Polygon', coordinates: coords });
    return obj
      .save()
      .then(() => {
        const query = new Parse.Query(TestObject);
        return query.get(obj.id);
      })
      .then(done.fail, () => done());
  });

  it('polygon three points minimum', done => {
    const coords = [[0, 0]];
    const obj = new TestObject();
    // use raw so we test the server validates properly
    obj.set('polygon', { __type: 'Polygon', coordinates: coords });
    obj.save().then(done.fail, () => done());
  });

  it('polygon three different points minimum', done => {
    const coords = [
      [0, 0],
      [0, 1],
      [0, 0],
    ];
    const obj = new TestObject();
    obj.set('polygon', new Parse.Polygon(coords));
    obj.save().then(done.fail, () => done());
  });

  it('polygon counterclockwise', done => {
    const coords = [
      [1, 1],
      [0, 1],
      [0, 0],
      [1, 0],
    ];
    const closed = [
      [1, 1],
      [0, 1],
      [0, 0],
      [1, 0],
      [1, 1],
    ];
    const obj = new TestObject();
    obj.set('polygon', new Parse.Polygon(coords));
    obj
      .save()
      .then(() => {
        const query = new Parse.Query(TestObject);
        return query.get(obj.id);
      })
      .then(result => {
        const polygon = result.get('polygon');
        equal(polygon instanceof Parse.Polygon, true);
        equal(polygon.coordinates, closed);
        done();
      }, done.fail);
  });

  describe('with location', () => {
    if (process.env.PARSE_SERVER_TEST_DB !== 'postgres') {
      beforeEach(async () => await TestUtils.destroyAllDataPermanently());
    }

    it('polygonContain query', done => {
      const points1 = [
        [0, 0],
        [0, 1],
        [1, 1],
        [1, 0],
      ];
      const points2 = [
        [0, 0],
        [0, 2],
        [2, 2],
        [2, 0],
      ];
      const points3 = [
        [10, 10],
        [10, 15],
        [15, 15],
        [15, 10],
        [10, 10],
      ];
      const polygon1 = new Parse.Polygon(points1);
      const polygon2 = new Parse.Polygon(points2);
      const polygon3 = new Parse.Polygon(points3);
      const obj1 = new TestObject({ boundary: polygon1 });
      const obj2 = new TestObject({ boundary: polygon2 });
      const obj3 = new TestObject({ boundary: polygon3 });
      Parse.Object.saveAll([obj1, obj2, obj3])
        .then(() => {
          const where = {
            boundary: {
              $geoIntersects: {
                $point: { __type: 'GeoPoint', latitude: 0.5, longitude: 0.5 },
              },
            },
          };
          return request({
            method: 'POST',
            url: Parse.serverURL + '/classes/TestObject',
            body: { where, _method: 'GET' },
            headers: {
              'X-Parse-Application-Id': Parse.applicationId,
              'X-Parse-Javascript-Key': Parse.javaScriptKey,
              'Content-Type': 'application/json',
            },
          });
        })
        .then(resp => {
          expect(resp.data.results.length).toBe(2);
          done();
        }, done.fail);
    });

    it('polygonContain query no reverse input (Regression test for #4608)', done => {
      const points1 = [
        [0.25, 0],
        [0.25, 1.25],
        [0.75, 1.25],
        [0.75, 0],
      ];
      const points2 = [
        [0, 0],
        [0, 2],
        [2, 2],
        [2, 0],
      ];
      const points3 = [
        [10, 10],
        [10, 15],
        [15, 15],
        [15, 10],
        [10, 10],
      ];
      const polygon1 = new Parse.Polygon(points1);
      const polygon2 = new Parse.Polygon(points2);
      const polygon3 = new Parse.Polygon(points3);
      const obj1 = new TestObject({ boundary: polygon1 });
      const obj2 = new TestObject({ boundary: polygon2 });
      const obj3 = new TestObject({ boundary: polygon3 });
      Parse.Object.saveAll([obj1, obj2, obj3])
        .then(() => {
          const where = {
            boundary: {
              $geoIntersects: {
                $point: { __type: 'GeoPoint', latitude: 0.5, longitude: 1.0 },
              },
            },
          };
          return request({
            method: 'POST',
            url: Parse.serverURL + '/classes/TestObject',
            body: { where, _method: 'GET' },
            headers: {
              'X-Parse-Application-Id': Parse.applicationId,
              'X-Parse-Javascript-Key': Parse.javaScriptKey,
              'Content-Type': 'application/json',
            },
          });
        })
        .then(resp => {
          expect(resp.data.results.length).toBe(2);
          done();
        }, done.fail);
    });

    it('polygonContain query real data (Regression test for #4608)', done => {
      const detroit = [
        [42.631655189280224, -83.78406753121705],
        [42.633047793854814, -83.75333640366955],
        [42.61625254348911, -83.75149921669944],
        [42.61526926650296, -83.78161794858735],
        [42.631655189280224, -83.78406753121705],
      ];
      const polygon = new Parse.Polygon(detroit);
      const obj = new TestObject({ boundary: polygon });
      obj
        .save()
        .then(() => {
          const where = {
            boundary: {
              $geoIntersects: {
                $point: {
                  __type: 'GeoPoint',
                  latitude: 42.624599,
                  longitude: -83.770162,
                },
              },
            },
          };
          return request({
            method: 'POST',
            url: Parse.serverURL + '/classes/TestObject',
            body: { where, _method: 'GET' },
            headers: {
              'X-Parse-Application-Id': Parse.applicationId,
              'X-Parse-Javascript-Key': Parse.javaScriptKey,
              'Content-Type': 'application/json',
            },
          });
        })
        .then(resp => {
          expect(resp.data.results.length).toBe(1);
          done();
        }, done.fail);
    });

    it('polygonContain invalid input', done => {
      const points = [
        [0, 0],
        [0, 1],
        [1, 1],
        [1, 0],
      ];
      const polygon = new Parse.Polygon(points);
      const obj = new TestObject({ boundary: polygon });
      obj
        .save()
        .then(() => {
          const where = {
            boundary: {
              $geoIntersects: {
                $point: { __type: 'GeoPoint', latitude: 181, longitude: 181 },
              },
            },
          };
          return request({
            method: 'POST',
            url: Parse.serverURL + '/classes/TestObject',
            body: { where, _method: 'GET' },
            headers: {
              'X-Parse-Application-Id': Parse.applicationId,
              'X-Parse-Javascript-Key': Parse.javaScriptKey,
            },
          });
        })
        .then(done.fail, () => done());
    });

    it('polygonContain invalid geoPoint', done => {
      const points = [
        [0, 0],
        [0, 1],
        [1, 1],
        [1, 0],
      ];
      const polygon = new Parse.Polygon(points);
      const obj = new TestObject({ boundary: polygon });
      obj
        .save()
        .then(() => {
          const where = {
            boundary: {
              $geoIntersects: {
                $point: [],
              },
            },
          };
          return request({
            method: 'POST',
            url: Parse.serverURL + '/classes/TestObject',
            body: { where, _method: 'GET' },
            headers: {
              'X-Parse-Application-Id': Parse.applicationId,
              'X-Parse-Javascript-Key': Parse.javaScriptKey,
            },
          });
        })
        .then(done.fail, () => done());
    });
  });
});

describe_only_db('mongo')('Parse.Polygon testing', () => {
  const Config = require('../lib/Config');
  let config;
  beforeEach(async () => {
    if (process.env.PARSE_SERVER_TEST_DB !== 'postgres') {
      await TestUtils.destroyAllDataPermanently();
    }
    config = Config.get('test');
    config.schemaCache.clear();
  });
  it('support 2d and 2dsphere', done => {
    const coords = [
      [0, 0],
      [0, 1],
      [1, 1],
      [1, 0],
      [0, 0],
    ];
    // testings against REST API, use raw formats
    const polygon = { __type: 'Polygon', coordinates: coords };
    const location = { __type: 'GeoPoint', latitude: 10, longitude: 10 };
    const databaseAdapter = config.database.adapter;
    return reconfigureServer({
      appId: 'test',
      restAPIKey: 'rest',
      publicServerURL: 'http://localhost:8378/1',
      databaseAdapter,
    })
      .then(() => {
        return databaseAdapter.createIndex('TestObject', { location: '2d' });
      })
      .then(() => {
        return databaseAdapter.createIndex('TestObject', {
          polygon: '2dsphere',
        });
      })
      .then(() => {
        return request({
          method: 'POST',
          url: 'http://localhost:8378/1/classes/TestObject',
          body: {
            _method: 'POST',
            location,
            polygon,
            polygon2: polygon,
          },
          headers: defaultHeaders,
        });
      })
      .then(resp => {
        return request({
          method: 'POST',
          url: `http://localhost:8378/1/classes/TestObject/${resp.data.objectId}`,
          body: { _method: 'GET' },
          headers: defaultHeaders,
        });
      })
      .then(resp => {
        equal(resp.data.location, location);
        equal(resp.data.polygon, polygon);
        equal(resp.data.polygon2, polygon);
        return databaseAdapter.getIndexes('TestObject');
      })
      .then(indexes => {
        equal(indexes.length, 4);
        equal(indexes[0].key, { _id: 1 });
        equal(indexes[1].key, { location: '2d' });
        equal(indexes[2].key, { polygon: '2dsphere' });
        equal(indexes[3].key, { polygon2: '2dsphere' });
        done();
      }, done.fail);
  });

  it('polygon coordinates reverse input', done => {
    const Config = require('../lib/Config');
    const config = Config.get('test');

    // When stored the first point should be the last point
    const input = [
      [12, 11],
      [14, 13],
      [16, 15],
      [18, 17],
    ];
    const output = [
      [
        [11, 12],
        [13, 14],
        [15, 16],
        [17, 18],
        [11, 12],
      ],
    ];
    const obj = new TestObject();
    obj.set('polygon', new Parse.Polygon(input));
    obj
      .save()
      .then(() => {
        return config.database.adapter._rawFind('TestObject', { _id: obj.id });
      })
      .then(results => {
        expect(results.length).toBe(1);
        expect(results[0].polygon.coordinates).toEqual(output);
        done();
      });
  });

  it('polygon loop is not valid', done => {
    const coords = [
      [0, 0],
      [0, 1],
      [1, 0],
      [1, 1],
    ];
    const obj = new TestObject();
    obj.set('polygon', new Parse.Polygon(coords));
    obj.save().then(done.fail, () => done());
  });
});
