import { StoreObject } from './storeObject';
import { ArrayType } from '../../typeSystem/array_type';
import { ArrayStoreValue } from './value/array_store_value';
import { Location } from "../../memory/location";
import { IStoreValue } from './value/istore_value';
import { StoreValue } from './value/store_value';

export class StoreObjectArray extends StoreObject {

  constructor (type: ArrayType, private _lines:number, private _columns:number, private loc_addresses: number[], readOnly = false) {
    super(type, -1, readOnly);
  }

  get lines () {
    return this._lines;
  }

  get columns () {
    return this._columns;
  }

  isCompatible (another: IStoreValue): boolean {
    if(another instanceof ArrayStoreValue) {
      const cols = another.columns == null ? 0 :  another.columns;
      if(this.lines === another.lines && this.columns === cols) {
          return super.isCompatible(another);
      }
    }
    return false;
  }

  get isVector () {
    return (this.type as ArrayType).dimensions === 1;
  }

  /**@override 
     * Returns the list of values stored by this array.
     * All returned values are compatible with @prop{StoreObject.type}
    */
  get value () {
    const values = [];
    for(let i = 0; i < this.addresses.length; i += 1) {
      const address = Location.find(this.addresses[i]);
      if (address != null) {
        values.push(address.value);
      } else {
        throw new Error("!!!Critical Error: variable "+this.id+" does not have a valid address. Loc-Address "+ this.locAddress);
      }
    }
    return values;
  }

  destroy () {
    let bool =  true;
    for(let i = 0; i < this.addresses.length; i += 1) {
      bool = bool && Location.deallocate(this.addresses[i]);
    }
    return bool;
  }

  /**
   * @override
   * 
   * Returns the address that contains a list of all addresses of the values present in the array
   */
  get locAddress (): number {
    throw new Error("!!!Internal Error: Cannot invoke locAddress on StoreObjectArray");
  }

  protected get addresses (): number[] {
    return this.loc_addresses;
  }

  getAt (line: number, column?: number): any {
    if(this.isVector) {
      if(column != null) {
        throw new Error(this.id + " is not a matrix!");
      }
      column = line;
      line = 0;
    } else if (column == null) {
      // this is a whole line...
      const values = [];
      for(let col = 0; col < this.columns; col += 1) {
        const index = this.getIndexOf(line, col);
        const address = Location.find(this.addresses[index])!;
        values.push(address.value);
      }
      return values;
    }
    const index = this.getIndexOf(line, column);
    const address = Location.find(this.addresses[index])!;
    return address.value;
  }

  setAt (value: StoreValue, line:number, column?: number): void {
    let used_dims = 1;
    if(column != null) {
      used_dims += 1;
    }
    if(!(this.type as ArrayType).canAccept(value.type, used_dims)) {
      throw new Error("!!!Internal Error: Attempting to insert an invalid value inside array "+this.id);
    }
    if(this.isVector) {
      if(column != null) {
        throw new Error(this.id + " is not a matrix!");
      }
      column = line;
      line = 0;
    } else if (column == null) {
      throw new Error("!!!Internal Error: Attempting to insert a line into matrix "+ this.id );
    }
    const pos = this.getIndexOf(line, column);
    Location.updateAddress(this.addresses[pos], value.get());
  }

  protected getIndexOf (line: number, column: number): number {
    return line * this.columns + column;
  }

  getLocAddressOf (line: number, column?: number): number | number[] {
    if(this.isVector) {
      if(column != null) {
        throw new Error(this.id + " is not a matrix!");
      }
      column = line;
      line = 0;
    } else if (column == null) {
      // this is a whole line...
      const values: number[] = [];
      for(let col = 0; col < this.columns; col += 1) {
        const index = this.getIndexOf(line, col);
        values.push(this.addresses[index]);
      }
      return values;
    }
    const index = this.getIndexOf(line, column);
    return this.addresses[index];
  }
}