import chai from 'chai';
import path from 'path';
import * as ms from '@modelx/data';
import sinonChai from 'sinon-chai';
import chaiAsPromised from 'chai-as-promised';
import { LSTMTimeSeries, } from './index';
const expect = chai.expect;
const independentVariables = [
  'Passengers',
];
const dependentVariables = [
  'Passengers',
];
const columns = independentVariables;//.concat(dependentVariables);
let csvData;
let DataSet;
let x_matrix;
let y_matrix;
let train_size;
let test_size;
let train_x_data;
let test_x_data;
let trainDataSet;
let testDataSet;
let x_matrix_test;
let TSTS;
let TSTSStateful;
let TSTSONE;
let TSTSStatefulONE;
let accuracyTest = {};
let evals;
const ds = [
  [ 1, ], [ 2, ], [ 3, ], [ 4, ], [ 5, ], [ 6, ], [ 7, ], [ 8, ], [ 9, ], [ 10, ],
];

const fit= {
  epochs: 10,
  batchSize: 1,
};

chai.use(sinonChai);
chai.use(chaiAsPromised);
function scaleColumnMap(columnName) {
  return {
    name: columnName,
    options: {
      strategy: 'scale',
      scaleOptions: {
        strategy:'standard',
      },
    },
  };
}
async function getModelAccuracy(preddata){
  const m = await preddata.model.train(x_matrix);
  const testData = preddata.model.getTimeseriesDataSet(x_matrix_test);
  // const preInputShape = LSTMTimeSeries.getInputShape(preddata.input);
  // console.log({ testData })
  // console.log({preInputShape})
  // console.log('preddata.input',preddata.input)
  // console.log('testData.x_matrix',testData.x_matrix)
  // const predictions = await preddata.model.predict(preddata.input);
  const predictions = await preddata.model.predict(testData.x_matrix);
  const predictions_unscaled = predictions.map(pred => [DataSet.scalers.get('Passengers').descale(pred[ 0 ]),]);
  const actuals_unscaled = testData.y_matrix.map(act => [DataSet.scalers.get('Passengers').descale(act[ 0 ]),]);
  // let results = ms.DataSet.reverseColumnMatrix({
  //   vectors: predictions_unscaled,
  //   labels: dependentVariables,
  // });
  return({
    model: preddata.modelname,
    // predictions,
    // actuals_unscaled,
    // predictions_unscaled,
    // results,
    accuracy: (ms.util.rSquared(
      ms.util.pivotVector(actuals_unscaled)[ 0 ], //actuals,
      ms.util.pivotVector(predictions_unscaled)[ 0 ], //estimates,
    ) * 100), //.toFixed(2)+'%',
  });
}
/** @test {LSTMTimeSeries} */
describe('LSTMTimeSeries', function () {
  beforeAll(async function () {
    /*
    csvData = [ 
      { Month: '1949-01', Passengers: 112 },
      { Month: '1949-02', Passengers: 118 },
      { Month: '1949-03', Passengers: 132 },
      { Month: '1949-04', Passengers: 129 },
      ...
      ];
    scaledData = [ 
      { Month: '1949-01', Passengers: -1.4028822039369186 },
      { Month: '1949-02', Passengers: -1.3528681653893018 },
      ...
    ]
    */
   const fpath = `${path.join(__dirname, '/test/mock/data/international-airline-passengers-no_footer.csv')}`;

    csvData = await ms.csv.loadCSV(fpath);
    DataSet = new ms.DataSet(csvData);
    const scaledData = DataSet.fitColumns({
      columns: columns.map(scaleColumnMap),
      returnData: false,
    });
    //{ train_size: 96, test_size: 48 }
    train_size = parseInt(DataSet.data.length * 0.67);
    test_size = DataSet.data.length - train_size;
    train_x_data = DataSet.data.slice(0, train_size);
    test_x_data = DataSet.data.slice(train_size, DataSet.data.length);
    trainDataSet = new ms.DataSet(train_x_data);
    testDataSet = new ms.DataSet(test_x_data);
    x_matrix = trainDataSet.columnMatrix(independentVariables);
    x_matrix_test = testDataSet.columnMatrix(independentVariables);
    
    /*
    x_matrix = [ 
      [ -1.4028822039369186 ],
      [ -1.3528681653893018 ],
      [ -1.2361687421115288 ],
      ...
    ]; 
    x_matrix_test: = [ 
      [ 0.2892594335907897 ],
      [ 0.17256001031301674 ],
      [ 0.6310220303328392 ],
      ...
    ]; 
    */
    TSTS = new LSTMTimeSeries({
      lookBack: 3,
      fit,
    });
    TSTSStateful = new LSTMTimeSeries({
      lookBack: 3,
      stateful: true,
      fit,
    });
    TSTSONE = new LSTMTimeSeries({
      lookBack: 1,
      fit,
    });
    TSTSStatefulONE = new LSTMTimeSeries({
      lookBack: 1,
      stateful: true,
      fit,
    });
    evals = [
      {
        model: TSTS,
        modelname: 'TSTS',
      },
      {
        model: TSTSStateful,
        modelname: 'TSTSStateful',
      },
      {
        model: TSTSONE,
        modelname: 'TSTSONE',
      },
      {
        model: TSTSStatefulONE,
        modelname: 'TSTSStatefulONE',
      },
    ];
    return true;
  }, 120000);
  /** @test {LSTMTimeSeries#createDataset} */
  describe('static createDataset', () => {
    const lookback = 3;
    it('should return timeseries datasets', () => {
      const [datax, datay, ] = LSTMTimeSeries.createDataset(ds);
      const [ datax2, datay2, ] = LSTMTimeSeries.createDataset(ds, lookback);
      expect(datax).to.have.lengthOf(datay.length);
      expect(datax2).to.have.lengthOf(datay2.length);
      expect(datax[ 0 ]).to.have.lengthOf(1);
      expect(datax2[ 0 ]).to.have.lengthOf(lookback);
    });
  });
  /** @test {LSTMTimeSeries#getTimeseriesShape} */
  describe('static getTimeseriesShape', () => {
    const [datax, datay,] = LSTMTimeSeries.createDataset(ds, 3);
    it('should calculate timeseries shape', () => {
      const tsShape = LSTMTimeSeries.getTimeseriesShape.call({
        getInputShape: LSTMTimeSeries.getInputShape,
        settings: {
          timeSteps: 1,
          mulitpleTimeSteps:false,
        },
      }, datax);
      const tsShape2 = LSTMTimeSeries.getTimeseriesShape.call({
        getInputShape: LSTMTimeSeries.getInputShape,
        settings: {
          timeSteps: 1,
          mulitpleTimeSteps:true,
        },
      }, datax);
      const tsShape3 = LSTMTimeSeries.getTimeseriesShape.call({
        getInputShape: LSTMTimeSeries.getInputShape,
        settings: {
          timeSteps: 1,
          stateful:true,
        },
      }, datax);
      expect(tsShape).to.eql([6, 1, 3,]);
      expect(tsShape2).to.eql([6, 3, 1,]);
      expect(tsShape3).to.eql([6, 3, 1,]);
    });
  });
  /** @test {LSTMTimeSeries#getTimeseriesDataSet} */
  describe('static getTimeseriesDataSet', () => {
    const [datax, datay,] = LSTMTimeSeries.createDataset(ds, 3);
    it('should return timeseries data', () => {
      const tsShape = LSTMTimeSeries.getTimeseriesDataSet.call({
        getInputShape: LSTMTimeSeries.getInputShape,
        settings: {
          timeSteps: 1,
          mulitpleTimeSteps:true,
        },
      }, ds, 3);
      expect(tsShape.yShape).to.eql([6, 1,]);
      expect(tsShape.xShape).to.eql([6, 3,]);
      expect(tsShape.y_matrix).to.eql(datay);
      // expect(tsShape.x_matrix).to.eql(datax);
      // console.log({ tsShape, });
    });
  });
  /** @test {LSTMTimeSeries#constructor} */
  describe('constructor', () => {
    it('should export a named module class', () => {
      const NN = new LSTMTimeSeries();
      const NNConfigured = new LSTMTimeSeries({ test: 'prop', });
      expect(LSTMTimeSeries).to.be.a('function');
      expect(NN).to.be.instanceOf(LSTMTimeSeries);
      expect(NNConfigured.settings.test).to.eql('prop');
    });
  });
  /** @test {LSTMTimeSeries#predict} */
  describe('async predict', () => {
    it('should allow for stateless predictions with one step time windows', async () => {
      const accr = await getModelAccuracy({ model: TSTSONE, modelname: 'TSTSONE', });
      // console.log({ accr });
      expect(accr.accuracy).to.be.ok;
      return true;
    },120000);
    it('should allow for stateless predictions with multiple step time windows', async () => {
      const accr = await getModelAccuracy({ model: TSTS, modelname: 'TSTSONE', });
      // console.log({ accr, });
      expect(accr.accuracy).to.be.ok;
      return true;
    },120000);
    // it('should make stateful predictions', async () => {
    //   const accr = await getModelAccuracy({ model: TSTSStateful, modelname: 'TSTSStateful', });
    //   const accr2 = await getModelAccuracy({ model: TSTSStatefulONE, modelname: 'TSTSStatefulONE', });
    //   // console.log({ accr, accr2 });
    //   expect(accr.accuracy).to.be.ok;
    //   expect(accr2.accuracy).to.be.ok;
    //   return true;
    // });
    it('should make single predictions', async () => {
      const testData = TSTSONE.getTimeseriesDataSet(x_matrix_test);
      const predictions = await TSTSONE.predict(testData.x_matrix[ 0 ]);
      expect(predictions).to.have.lengthOf(1);
      // console.log({ predictions });
      return true;
    },120000);
  });
  /** @test {LSTMTimeSeries#train} */
  describe('async train', () => {
    it('should train a model with supplied test data', async () => {
      const testData = TSTSONE.getTimeseriesDataSet(x_matrix_test);
      const LSTMTS = new LSTMTimeSeries({ layerPreference: 'custom', fit, });
      const matrices = LSTMTimeSeries.createDataset(x_matrix_test);
      const x = matrices[ 0 ];
      const y = matrices[ 1 ];
      await LSTMTS.train(x, y);
      const predictions = await TSTSONE.predict(testData.x_matrix[ 0 ]);
      const predictions_unscaled = predictions.map(pred => [DataSet.scalers.get('Passengers').descale(pred[ 0 ]),]);
      console.log({ predictions_unscaled });
      expect(predictions).to.have.lengthOf(1);
      expect(LSTMTS.layers).to.be.a('object');
      return true;
    },120000);
  });
  /** @test {LSTMTimeSeries#generateLayers} */
  describe('generateLayers', () => {
    // it('should generate a classification network', async () => {
    //   const predictions = await nnClassification.predict(input_x);
    //   const answers = await nnClassification.predict(input_x, {
    //     probability:false,
    //   });
    //   const shape = nnClassification.getInputShape(predictions);
    //   // console.log('nnClassification.layers', nnClassification.layers);
    //   // console.log({
    //   //   predictions,
    //   //   // probabilities,
    //   //   answers,
    //   //   // results,
    //   //   shape,
    //   // });
    //   expect(predictions).to.have.lengthOf(input_x.length);
    //   expect(nnClassification.layers).to.have.lengthOf(2);
    //   expect(shape).to.eql([5, 3,]);
    //   expect(answers[ 0 ]).to.eql(encodedAnswers[ 'Iris-setosa' ]);
    //   // expect(answers[ 1 ]).to.eql(encodedAnswers[ 'Iris-virginica' ]);
    //   // expect(answers[ 2 ]).to.eql(encodedAnswers[ 'Iris-versicolor' ]);
    //   // expect(answers[ 3 ]).to.eql(encodedAnswers[ 'Iris-setosa' ]);
    //   // expect(answers[ 4 ]).to.eql(encodedAnswers[ 'Iris-setosa' ]);
    //   return true;
    // });
    it('should generate a network from layers', async () => { 
      const LSTMTS = new LSTMTimeSeries({ layerPreference: 'custom', fit, });
      console.log('TSTSONE.layers', TSTSONE.layers);
      await LSTMTS.train(x_matrix, y_matrix, TSTSONE.layers);
      expect(LSTMTS.layers).to.be.a('object');
      return true;
    },120000);
  });
});