// Node
import * as fs from 'fs';
import * as stream from 'stream';
import * as zlib from 'zlib';

// Shared libraries
import * as Util from '@dra2020/util';
import * as Context from '@dra2020/context';
import * as LogAbstract from '@dra2020/logabstract';
import * as LogClient from '@dra2020/logclient';
import * as FSM from '@dra2020/fsm';
import * as Storage from '@dra2020/storage';
import * as DB from '@dra2020/dbabstract';
import * as DT from '@dra2020/dra-types';

import * as DD from '../lib/all';

class FsmTest extends FSM.Fsm
{
  constructor(env: DD.Environment)
  {
    super(env);
  }

  tick(): void
  {
    if (this.ready)
    {
      console.log('dynamodb test: exiting');
      process.exit(0);
    }
  }
}

class FsmLoudOp extends FSM.Fsm
{
  col: DB.DBCollection;
  op: any;
  fsm: any;

  constructor(op: any)
  {
    super(env);
    this.col = op.col as DB.DBCollection;
    this.op = op;
  }

  tick(): void
  {
    if (this.ready && this.isDependentError)
    {
      console.log(`${this.op.op}: with query ${JSON.stringify(this.op.q)}: failure`);
      this.setState(FSM.FSM_DONE);
    }
    else if (this.ready)
    {
      switch (this.state)
      {
        case FSM.FSM_STARTING:
          switch (this.op.op)
          {
            case 'find':
              this.fsm = db.createFind(this.col, this.op.q);
              break;
            case 'update':
              this.fsm = db.createUpdate(this.col, this.op.q, this.op.u);
              break;
            case 'unset':
              this.fsm = db.createUnset(this.col, this.op.q, this.op.u);
              break;
            case 'delete':
              this.fsm = db.createDelete(this.col, this.op.q);
              break;
            case 'query':
              this.fsm = db.createQuery(this.col, this.op.q);
              break;
          }
          this.waitOn(this.fsm);
          this.setState(FSM.FSM_PENDING);
          break;

        case FSM.FSM_PENDING:
          switch (this.op.op)
          {
            case 'find':
              if (this.fsm.result)
                console.log(`${this.op.op}: with query ${JSON.stringify(this.op.q)}: ${JSON.stringify(this.fsm.result)}`);
              else
                console.log(`${this.op.op}: with query ${JSON.stringify(this.op.q)}: (null)`);
              break;
            case 'delete':
            case 'unset':
            case 'update':
              console.log(`${this.op.op}: with query ${JSON.stringify(this.op.q)}: success`);
              break;
            case 'query':
              if (this.fsm.result)
                console.log(`${this.op.op}: with query ${JSON.stringify(this.op.q)}: ${JSON.stringify(this.fsm.result)}`);
              else
                console.log(`${this.op.op}: with query ${JSON.stringify(this.op.q)}: (null)`);
              break;
          }
          this.setState(FSM.FSM_DONE);
          break;
      }
    }
  }
}

class FsmStreamEater extends FSM.Fsm
{
  fsmArray: FSM.FsmArray;

  constructor(env: DD.Environment, col: DD.DynamoCollection)
  {
    super(env);
    this.fsmArray = env.db.createStream(col);
  }

  tick(): void
  {
    if (this.ready && this.state == FSM.FSM_STARTING)
    {
      if (this.fsmArray.a.length > 0)
        console.log(`dbstream output: ${JSON.stringify(this.fsmArray.a)}`);
      this.fsmArray.reset();
      this.waitOn(this.fsmArray);
    }
  }
}

let env: DD.Environment = { context: null, log: null, fsmManager: null, storageManager: null, db: null };
env.context = Context.create();
env.log = LogClient.create(env);
env.fsmManager = new FSM.FsmManager();
let tester = new FsmTest(env);
let db: DB.DBClient = new DD.DynamoClient(env);
tester.waitOn(db);

Object.keys(DT.Schemas).forEach((name: string) => {
    DT.Schemas[name].Collection = db.createCollection(`test-${name}`, DT.Schemas[name]);
    tester.waitOn(DT.Schemas[name].Collection);
  });

let users = DT.Schemas['users'].Collection;
tester.waitOn(users);
let maps = DT.Schemas['state'].Collection;
tester.waitOn(maps);

let eater = new FsmStreamEater(env, maps);
tester.waitOn(eater);

let x = Math.trunc(10000 * Math.random());
function uid(): string { return `map${x++}` }

let ops: any[] = [
  { col: users, op: 'update', q: { id: 'user1' }, u: { email: 'a@b' } },
  { col: users, op: 'update', q: { id: 'user1' }, u: { id: 'user1', email: 'a@b' } },
  { col: users, op: 'update', q: { id: 'user3' }, u: { email: 'x@y' } },
  { col: users, op: 'query', q: { } },
  { col: users, op: 'update', q: { id: 'user1' }, u: { roles: [ 'insider' ] } },
  { col: users, op: 'find', q: { id: 'user1' } },
  { col: users, op: 'find', q: { email: 'a@b' } },
  { col: users, op: 'find', q: { id: 'user2' } },
  { col: users, op: 'find', q: { email: 'c@d' } },
  { col: users, op: 'delete', q: { id: 'user1' } },
  { col: users, op: 'delete', q: { id: 'user1' } },
  { col: users, op: 'delete', q: { id: 'user2' } },
  { col: users, op: 'update', q: { id: 'user1' }, u: { roles: [ 'insider' ] } },
  { col: users, op: 'find', q: { id: 'user1' } },
  { col: users, op: 'update', q: { id: 'user1' }, u: { admin: true, roles: [ 'outsider' ] } },
  { col: users, op: 'find', q: { id: 'user1' } },
  { col: users, op: 'unset', q: { id: 'user1' }, u: { admin: false, roles: [ 'outsider' ] } },
  { col: users, op: 'find', q: { id: 'user1' } },
  { col: maps, op: 'update', q: { id: uid() }, u: { } },
  { col: maps, op: 'update', q: { id: uid(), createdBy: 'user1' }, u: { labels: [ 'MA' ] } },
  { col: maps, op: 'update', q: { id: uid(), createdBy: 'user1' }, u: { published: 'true' } },
  { col: maps, op: 'update', q: { id: uid(), createdBy: 'user1' }, u: { published: 'true' } },
  { col: maps, op: 'update', q: { id: uid(), createdBy: 'user1' }, u: { xprops: { state: 'MA' } } },
  { col: maps, op: 'update', q: { id: uid(), createdBy: 'user1' }, u: { xprops: { state: 'MA', datasource: '2010' } } },
  { col: maps, op: 'query', q: { published: 'true' } },
  { col: maps, op: 'update', q: { id: uid(), createdBy: 'user1' }, u: { accessMap: { 'uid1': { userIDs: [], perm: 3 }} } },
  { col: maps, op: 'update', q: { id: uid(), createdBy: 'user1' }, u: { revisions: [ { id: 'rev1', modifyTime: 'tt' } ] } },
  { col: maps, op: 'query', q: { createdBy: 'user1' } },
];

let prev: FsmLoudOp = null;
for (let i: number = 0; i < ops.length; i++)
{
  let next = new FsmLoudOp(ops[i]);
  next.waitOn(prev);
  tester.waitOn(next);
  prev = next;
}
