import path = require ('path');

import esprima = require ('esprima');

import BShandler = require ('../browser-sync-handler');
import CoverageObject = require ('./coverage/coverage-object');
import CoverageFunction = require ('./coverage/coverage-function');
import CoverageBranch = require ('./coverage/coverage-branch');
import CoverageStatement = require ('./coverage/coverage-statement');
import LeenaConfiguration = require ('../config');
import utils = require ('../utils');


class JSFile {
  // Attributes that refer to the original application (watcher)
  public fileName : string;
  public pathFile : string;

  // Attributes that refer to the temporary application (watcher)
  public tempFileName : string;
  public pathTempFile : string;

  // Content of 'pathFile'
  public content : string;

  // AST of 'pathFile'
  public originalAST;

  // Content of 'pathTempFile'
  public instrumentedContent : string;

  // AST of 'instrumentedContent'
  public instrumentedAST;

  // Coverage object name
  public coverageObjectName : string;

  // Coverage object value (generated by Istanbul)
  public coverageObject : CoverageObject;

  // Leena configuration
  private leenaConfig : LeenaConfiguration;


  constructor (fileInfo : BShandler.PropertyReturnValue, leenaConfig : LeenaConfiguration,
               cb : (err : Error, res : any) => void) {
    this.pathFile = path.normalize (fileInfo.pathFile);
    this.fileName = path.basename (this.pathFile);

    this.pathTempFile = path.normalize (fileInfo.pathTempFile);
    this.tempFileName = path.basename (this.pathTempFile);

    this.leenaConfig = leenaConfig;

    this.initializeAttributes ();

    this.coverageObject = new CoverageObject (
      this.coverageObjectName,
      this.pathTempFile,
      this.leenaConfig,
      this.originalAST,
      cb
    );
  }

  public containsFunction (functionName : string) : boolean {
    return this.coverageObject.containsFunction (functionName);
  }

  public executeFunction (functionName : string,
                          cb : (err : Error, res) => void) : void {
    this.coverageObject.executeFunction (functionName, cb);
  }

  public updateFunctionInstance (functionName : string, functionI : CoverageFunction) : void {
    this.coverageObject.updateFunctionInstance (functionName, functionI);
  }

  public getFunctionInstance (functionName : string) : CoverageFunction {
    return this.coverageObject.getFunctionInstance (functionName);
  }

  private initializeAttributes () : void {
    // Set 'content' attribute by reading the file
    if ((this.content = utils.readFile (this.pathFile)) === null) {
      throw new Error ('JSFile, unable to initialize content');
    }

    // Set 'originalAST' attribute using Esprima
    try {
      this.originalAST = esprima.parse (this.content, {
        loc: true
      });
    } catch (e) {
      throw e;
    }

    // Set 'instrumentedContent' by reading the file
    // File is already instrumented by 'add' method of './src/browser-sync-handler'
    if ((this.instrumentedContent = utils.readFile (this.pathTempFile)) === null) {
      throw new Error ('JSFile, unable to initialize instrumentedContent');
    }

    // Set 'instrumentedAST' attribute using Esprima
    try {
      this.instrumentedAST = esprima.parse (this.instrumentedContent);
    } catch (e) {
      throw e;
    }

    // Set 'coverageObjectName'
    this.coverageObjectName = this.getCoverageObjectName ();
    if (this.coverageObjectName === null) {
      throw new Error ('Unable to get coverage object name');
    }
  }

  public update () : void {
    // When the file will be changed, only attributes on 'initialize' method
    // will be changed:
    //   - content, originalAST
    //   - instrumentedContent, instrumentedAST
    this.initializeAttributes ();

    // These attributes won't be changed:
    //   - pathFile, fileName
    //   - pathTempFile, tempFileName

    // 'coverageObject' will be update with its method
    this.coverageObject.update ();
  }

  private getCoverageObjectName () : string {
    var coverageObjName : string;

    try {
      if (this.instrumentedAST.body.length > 0 &&
        this.instrumentedAST.body[0].declarations.length > 0) {
        coverageObjName = this.instrumentedAST.body[0].declarations[0].id.name;
      }
    } catch (e) {
      coverageObjName = null;
    }

    return coverageObjName;
  }
}

export = JSFile;
