var Product = Class.create({
  CLASSDEF: {
      name: 'Product'
  },
  
  initialize: function P_initialize(id, options) {
    this.id = id;
    this.options = options;
    this.name = options.name;
    this.description = options.desc;
    
    this.type = d.productTypes[options.type];
    if(this.type.noCategores) {
      this.categoryId = -1;
      this.subCategoryId = -1;
    } else {
      this.categoryId = options.cid;
      this.subCategoryId = options.sc;
    }
    
    this.availableProcesses = {};
    this.displayImage = options.di;
    
    this.price = [parseFloat(options.p[0]), parseFloat(options.p[1]), parseFloat(options.p[2])];
    
    this.taxExempt = options.te;
    
    this.minQty = options.mq;
    if(this.minQty == -1) {
      this.minQty = this.type.minQty;
      this.bundleSize = this.type.bundleSize;
    } else {
      this.bundleSize = options.bs;
    }
    
    this.views = new MapList(this);
  
    for(var i=0; i < options.v.size(); i++) {
      this.views.add(new ProductView(this, options.v[i], i));
    }
    
    log("Product(): Loaded Views");
    
    this.defaultView = this.views.byId[options.sdv];
    this.displayView = this.views.byId[options.sdisv];
    
    this.colors = new MapList(this);
    
    
    var pc = options.c;
    for(var i=0;i<pc.length;i++) {
      var l = new ProductColor(this, pc[i]);
      this.colors.add(l);
    }
    log("Product(): Loaded Colors");
    this.defaultColor = this.colors.byId[options.dc];
    
    this.hasDerivedViews = options.vad;
    
    
    this.optionsByProdChosenOptId = {}; //mapping from product_chosen_option_id to FieldChoice
    this.fields = new MapList(this);
    for(var i=0; i < options.f.size(); i++) {
      var pf = new ProductField(this, options.f[i]);
      if(pf.fieldDef == null) {
        log("Dropping field " + options.f[i].id + " as it is not in the defn");
      } else {
        this.fields.add(pf);
      }
    }
    log("Product(): Loaded Fields");
    //dynamically add all fields that are default on yet are not in the product def...
    for(var i=0; i < this.type.fields.list.length; i++) {
      var fieldDef = this.type.fields.list[i];
      if(fieldDef.defaultOn && this.fields.byId[fieldDef.id] == null) {
        var pf = new ProductField(this, {id:fieldDef.id, setupDefaults:true, u:true, uDef: true, opts: fieldDef.getProductDefaultOptions()});
        this.fields.add(pf);
        log("Added defaultOn field " + fieldDef.name);
      }
    }
    this.limitSizeColors = false;
    if(options.scc != null) {
      this.sizeColorCombinations = options.scc;
      this.limitSizeColors = true;
    } else {
      this.sizeColorCombinations = null;
    }
    log(this);
  },
  
  getDisplayImageURL: function P_getDisplayImageURL() {
    if(this.displayImage != null) {
      return this.displayImage;
    } else {
      return this.displayView.getViewURL(2, this.defaultColor.productChosenOptionId, 1);
    }
  },
  
  getOverlayURL: function P_getOverlayURL(layoutId, color, size) {
    var l = this.layoutsById[layoutId];
    if(l != null) {
      var url = l.oUrl.replace(d.layoutViewUrlCID, color);
      return url.replace(d.layoutViewUrlS, size); //d.pathPrefix + 
    }
    return "";
  },
  
  getColor: function P_getColor(id) {
    return this.colorsById[id];
  },
  
  //get the first used process...
  getFirstProcessId: function P_getFirstProcessId() {
    for(var i=0; i < this.views.list.size(); i++) {
      var v = this.views.list[i];
      for(var j=0; j < v.areas.list.size(); j++) {
        var a= v.areas.list[j];
        for(var k=0; k < a.processes.list.size(); k++) {
          return a.processes.list[k].id;
        }
      }
    }
  },
  
  getSizeField: function P_getSizeField() {
    if(this.type.sizeField == null) {
      return null;
    }
    return this.fields.byId[this.type.sizeField.id];
  },
  
  usesMinQty: function P_usesMinQty() {  
    return (this.minQty > 1);
  },
  
  buildQtyDropdownHtml: function(curQty) {
    var items = [];
    var inc = this.bundleSize;
    var q = this.minQty;
    for(var i=0; i < 8; i++) {
      var ev = "";
      var clz = "";
      if(curQty != q) {
        ev = ' onmousemove="this.className=\'over\';" onmouseout="this.className=null;" ';
      } else {
        clz = ' class="alt"';
      }
      items.push('<li' + clz + ev + ' onmousedown="d.qtyDropDownSelected(' + q + ');">' + q + '</li>');
      q += inc;
      /*if(q == 25 || q == 50 || q == 100) {
        inc = q;
      } else {
        inc *= 2;
      }*/
    }
    return '<ul>' + items.join("\n") + '</ul>';
  },
  
  registerAvailableProcess: function P_registerAvailableProcess(processId) {
    this.availableProcesses[processId] = true;
  },
  
  canDecorate: function P_canDecorate(process) {
    return (this.availableProcesses[process.id] == true);
  },
  
  //get the disabled message.. for product it would be '%1s is not available on this %2s'
  //if we cant decorate because the product cant, use the products getDecProcDisabledMessage
  getDecProcDisabledMessage: function P_getDecProcDisabledMessage(process) {
    if(this.type.canDecorate(process)) {
      return process.name + " is not available on this " + this.name;
    } else {
      return this.type.getDecProcDisabledMessage(process);
    }
  }
});   

var ProductField = Class.create({
  CLASSDEF: {
      name: 'ProductField'
  },
  
  initialize: function PF_initialize(product, options) {
    this.product = product;
    this.valid = true;
    this.id = options.id; 
    this.sfid = options.sfid;
    this.fieldDef = this.product.type.fields.byId[this.id];
    if(this.fieldDef == null) return; //why bother continueing?
    
    this.used = options.u;
    this.priceDelta = options.d;
    this.usePriceDefaults = options.uDef;
    this.options = new MapList(this);
    
    this.multiOption = null;
    if(this.fieldDef.typeOptions.list) {
      //log("loading options for " + this.fieldDef.name);
      for(var i=0; i < options.opts.length; i++) {
        //log("loading option " + options.opts[i].id);
        var opt = this.options.add(new ProductFieldChoice(this, options.opts[i]));
        if(opt.def == null) { //invalid data...
          log("ERROR: ProductFieldChoice " + opt.id + " has missing def on product " + product.name);
          this.options.remove(opt.id);
        } else if(opt.def.isMulti && this.product.usesMinQty()) {
          log("NOTE: ProductFieldChoice " + opt.id + " has is multi but product uses min qty " + product.name);
          this.options.remove(opt.id);
        } else {
          if(opt.selected) {
            this.defaultOption = opt;
            //log("found default")
          }
          this.product.optionsByProdChosenOptId[opt.pcoid] = opt;
          if(opt.def.isMulti) {
            this.multiOption = opt;
          }
        }
      }
      if((options.opts.length ==0)&&(this.fieldDef.typeOptions.list)) {
        log("Invalid field " + this.fieldDef.name + " has no options");
        this.valid = false;
      }
      if(this.valid) {
        if(this.fieldDef.fieldType == FIELD_TYPE_PRODUCT_SIZE  && !this.product.usesMinQty() && this.multiOption == null && this.fieldDef.productType.ms && this.fieldDef.multiOption!=null) { //there is no multi option def for this product but the type says only show in multi option mode...
          log("Adding Multi Choice because type has multi only defined");
          this.options.add(new ProductFieldChoice(this, {id:this.fieldDef.multiOption.id, s:true }));
        }
        
        if(this.defaultOption == null) {
          log("ERROR: no default option loaded... using first option");
          this.defaultOption = this.options.list[0];
        }
      }
    }
    
  },
  
  //used when swapping products.. try and find a matching option....
  findOption: function(otherOption) {
    var foundOption = null;
    if(this.options.byId[otherOption.id] != null) {
      foundOption = this.options.byId[otherOption.id];
    } else {
      for(var i=0; i < this.options.list.length; i++) {
        var opt = this.options.list[i];
        if(opt.def.name == otherOption.def.name) {
          foundOption = opt;
          break;
        } else if(opt.def.value == otherOption.def.value) {
          foundOption = opt;
        }
      }
    }
    return foundOption;
  },
  
  //dynamically add multi option to support teamnames...
  addMultiOption: function PF_addMultiOption() {
    this.options.add(this.options.add(new ProductFieldChoice(this, {id:this.fieldDef.multiOption.id, s:true })));
  }
});

var ProductFieldChoice = Class.create({
  CLASSDEF: {
      name: 'ProductFieldChoice'
  },

  initialize: function PFC_initialize(field, options) {
    this.field = field;
    this.id = options.id; //product_option.id
    this.pcoid = options.pcoid; //product_chosen_option.id
    this.spcoId = options.spcoId;  //supplier_product_chosen_option.id
    this.def = field.product.type.optionsById[this.id];
    this.priceDelta = options.d;
    //this.sizeDelta = parseFloat(options.sd);
    //this.xOffset = parseInt(options.x, 10);
    //this.yOffset = parseInt(options.y, 10);
    this.selected = options.s;
    if(options.sub != null && options.sub.length > 0) {
      this.subs = {};
      var added = false;
      for(var i=0; i < options.sub.length; i++) {
        var pfsc = new ProductFieldSubChoice(this, options.sub[i]);
        if(pfsc.def != null) {
          this.subs[pfsc.id] = pfsc;
          added = true;
        } else {
          log("dropped missing product sub option " + pfsc.id);
        }
      }
      if(!added) this.subs = null;
    } else {
      this.subs = null;
    }
  },
  
  getSubOption: function PFC_getSubOption(id) {
    if(this.subs == null) return null;
    return this.subs[id];
  },
  
  //used when swapping products.. try and find a matching option....
  findOption: function(otherOption) {
    if(this.subs == null) {
      return null;
    }
    var foundOption = null;
    if(this.subs[otherOption.id] != null) {
      foundOption = this.subs[otherOption.id];
    } else {
      for(var i in this.subs) {
        var opt = this.subs[i];
        if(opt.def.name == otherOption.def.name) {
          foundOption = opt;
          break;
        } else if(opt.def.value == otherOption.def.value) {
          foundOption = opt;
        }
      }
    }
    return foundOption;
  }
});

var ProductFieldSubChoice = Class.create({
  CLASSDEF: {
      name: 'ProductFieldSubChoice'
  },

  initialize: function PFSC_initialize(pFieldChoice, options) {
    this.pFieldChoice = pFieldChoice;
    this.id = options.id; //product_option.id
    this.def = pFieldChoice.def.getSubOption(this.id);
    this.priceDelta = options.d;
   
    this.selected = options.s;
  }
});

var ProductView = Class.create({
  CLASSDEF: {
      name: 'ProductView'
  },
  
  initialize: function PV_initialize(product, options, viewIndex) {
    this.product = product;
    this.id = options.id;
    this.name = options.name;
    this.url = options.url;
    this.viewIndex = viewIndex;
  
    this.allowDesign = options.ad;
    this.allowView = options.av;
    
    this.offsetX = parseInt(options.x, 10);
    this.offsetY = parseInt(options.y, 10);
    
    this.areas = new MapList(this);

    this.availableProcesses = {};
    
    for(var i=0; i < options.a.size(); i++) {
      this.areas.add(new ProductViewArea(this, options.a[i], i));
    }
  },
  
  //allowStartUrl: a view can have a start url when loading existing configured items/custom products
  //allowScale: should we lookup and layout modifiers to see if the scale changes...
  getViewURL: function PV_getViewURL(size, colorId, scale) {
    var url = this.url.replace(d.layoutViewUrlCID, colorId);
    url = url.replace(d.layoutViewUrlS, size );
    return url.replace(d.layoutViewUrlSc, scale );
  },
  
  buildSelectorHtml: function PV_buildSelectorHtml(colorId, configuredView, customProduct) {
    if(!this.allowView) {
      return "";
    }
    var url = null;
    if(configuredView != null) {
      url = configuredView.getViewURL(11, true, false);
    } else if(customProduct!=null) {
      url = customProduct.getViewURL(this.id, 11, colorId, 1);
    } 
    if(url == null) {
      url = this.getViewURL(11, colorId, 1);
    }
    if((this.allowDesign)||(d.mode == DESIGNER_MODE_VIEW_CUSTOM_PRODUCT)) {
      var clz = (this.id == d.selectedViewId) ? "d_layout_selected" : "d_layout_unselected";
      return '<li id="d_l_' + this.id + '" class="' + clz + '" onclick="d.viewClick(' + this.id + ');" onmousemove="d.viewMouseMove(this);" onmouseout="d.viewMouseOut(this);"><span></span><b id="d_l_s_' + this.id + '">&nbsp;</b><img id="d_l_i_' + this.id + '" src="' + url + '" onload="d.checkAreaHighlightPosition(' + this.id + ');"/><div id="d_l_ah_' + this.id + '" class="sel_area_highlight" style="display:none;">&nbsp;</div><label>'+this.name+'</label></li>';
    } else {
      return '<li id="d_l_' + this.id + '" class="unselectable"><img id="d_l_i_' + this.id + '" src="' + url + '"/><label>'+this.name+'</label></li>';
    }
  },
  
  updateAreaSelectorHtml: function PV_updateAreaSelectorHtml() {
    if(this.areas.list.size() > 1) {
      var html = "<h4>" + ml("Areas") + "</h4><ul class='areas'>";
      for(var i=0; i < this.areas.list.size(); i++) {
        var area = this.areas.list[i];
        html += '<li><input type="radio" name="selected_area" onclick="d.selectCurrentArea(' + area.id + ', true);" id="a_sel_' + area.id + '" value="' + area.id + '"/> <label for="a_sel_' + area.id + '">' + area.name + '</label></li>';
      }
      html += '</ul>';
      $("area_selector_container").innerHTML = html;
      $("area_selector_container").style.display="";
    } else {
      $("area_selector_container").style.display="none";
    }
  },
  
  //get the first area using the currently selected process...
  getDefaultPricingProcess: function PV_getDefaultPricingProcess(defaultProcessId) {
    if(defaultProcessId == null) {
      defaultProcessId = d.userSelectedProcessId;
    }
    for(var i=0; i < this.areas.list.size(); i++) {
      var area = this.areas.list[i];
      if(defaultProcessId == null) {
        return area.processes.list[0];
      }
      if(area.processes.byId[defaultProcessId] != null) {
        return area.processes.byId[defaultProcessId];
      }
    }
    return null;
  },
  
  registerAvailableProcess: function PV_registerAvailableProcess(processId) {
    this.availableProcesses[processId] = true;
    this.product.registerAvailableProcess(processId);
  },
  
  canDecorate: function PV_canDecorate(process) {
    return (this.availableProcesses[process.id] == true);
  },
  
  //get the disabled message.. for view it would be '%1s is not available on the %2s of the %3s'
  //if we cant decorate because the product cant, use the products getDecProcDisabledMessage
  getDecProcDisabledMessage: function PV_getDecProcDisabledMessage(process) {
    if(this.product.canDecorate(process)) {
      return process.name + " is not available on the " + this.name + " of the " + this.product.name;
    } else {
      return this.product.getDecProcDisabledMessage(process);
    }
  }
});

var ProductViewArea = Class.create({
  CLASSDEF: {
      name: 'ProductViewArea'
  },
  
  initialize: function PVA_initialize(view, options, areaIndex) {
    this.view = view;
    this.id = options.id;
    this.areaIndex = areaIndex;
    
    this.name = options.name;
    
    this.l = options.l;
    this.t = options.t;
    this.w = options.w;
    this.h = options.h;
    
    this.actualWidth = options.aw;
    this.actualHeight = options.ah;
    
    
    this.canSetBgColor = options.bgc;
    
    
    if(this.h > this.w) {
      this.reScale = 380.0 / parseFloat(this.h);
    } else {
      this.reScale = 380.0 / parseFloat(this.w);
    }
    
    this.dH = parseInt(this.h * this.reScale, 10);
    this.dW = parseInt(this.w * this.reScale, 10);
    this.dL = parseInt((400 - this.dW)/2, 10);
    this.dT = parseInt((400 - this.dH)/2, 10);
    
    
    var mask = options.m;
    this.maskId = mask.id;
    this.maskUrl = mask.url;
    
    this.processes = new MapList(this);
    for(var i=0; i < options.p.size(); i++) {
      var opts = options.p[i];
      var ptp = this.view.product.type.processes.byId[opts.id];
      if(ptp != null) { //check that the process is still supported....
        this.processes.add(new ProductViewAreaProcess(this, opts));
        this.view.registerAvailableProcess(ptp.id);
      }
    }
    this.processes.resort();
  },
  
  getName: function PVA_getName() {
    return this.view.name + "-" + this.name + " (" + this.view.product.name + ")";
  },
  
  canDecorate: function PVA_canDecorate(process) {
    return (this.processes.byId[process.id] != null);
  },
  
  //get the disabled message.. for area it would be '%1s is not available on the %2s'
  //if we cant decorate because the view cant, use the views getDecProcDisabledMessage
  getDecProcDisabledMessage: function PVA_getDecProcDisabledMessage(process) {
    if(this.view.canDecorate(process)) {
      return process.name + " is not available on the " + this.name;
    } else {
      return this.view.getDecProcDisabledMessage(process);
    }
  },
  
  isInArea: function PVA_isInArea(x,y) {
    if(x > this.l && x < (this.l + this.w) && y > this.t && y < (this.t + this.h)) {
      return true;
    } else {
      return false;
    } 
  }
});

var ProductViewAreaProcess = Class.create({
  CLASSDEF: {
      name: 'ProductViewAreaProcess'
  },
  
  initialize: function PVAP_initialize(viewArea, options) {
    this.viewArea = viewArea;
    this.id = options.id;
    
    this.productTypeProcess = this.viewArea.view.product.type.processes.byId[this.id];
    this.process = this.productTypeProcess.process;
    
    this.perfectDPI = options.pdpi;
    this.minDPI = options.mdpi;
    
    if((this.perfectDPI == null)||(this.process.isWilcomEMB())) { //this process does not use DPI... use web dpi..
      this.perfectDPI = 192;
      this.minDPI = 192;
    }
    
    this.fullWidth = this.viewArea.actualWidth * this.perfectDPI;
    this.fullHeight = this.viewArea.actualHeight * this.perfectDPI;
    
    this.layoutScale = parseFloat(this.viewArea.w) / parseFloat(this.fullWidth);
    this.designScale = parseFloat(this.viewArea.dW) / parseFloat(this.fullWidth);
    
    this.prices = options.d;
    
    if(this.productTypeProcess.priceMode==2) {//price table
      this.priceTable = d.priceTables[options.pt];
      if(this.priceTable == null) {
        log("ERROR: unable to get price table '" + options.pt + "'");
      }
    }
  },
  
  //sort processes by zindex descending
  compare: function PVAP_compare(other) {
    if (other.process === undefined) return -1
    return  other.process.zIndex - this.process.zIndex;
  },
  
  //we charge for 1 decoration even if no decorations are used...
  getDefaultDecorationPrice: function PVAP_getDefaultDecorationPrice(colorType) {
    if(this.productTypeProcess.priceMode == 0) {
      return this.prices[colorType];
    } else if(this.productTypeProcess.priceMode == 1) {
      return this.prices[0];
    } else {
      //get the price of the default stitch count....
      if(this.priceTable == null) {
        log("ERROR: Unable to get price of default stitch count: priceTable is null");
      } else {
        log("using price table to get price of default stitch count (" + this.productTypeProcess.defaultStitchCount + ")");
        return this.priceTable.calcPrice(this.productTypeProcess.defaultStitchCount, d.currentCProduct.qty)[0];
      }
      return 0;
    }
  },
  
  canModifyDesign: function() {
    if(d.mode == DESIGNER_MODE_AMEND) {
      if(this.productTypeProcess.priceMode == 2) { //price table
        return false;
      }
    }
    return true;
  }
});

var ProductColor = Class.create({
  CLASSDEF: {
      name: 'ProductColor'
  },
  
  initialize: function PClr_initialize(product, options) {
    this.product = product;
    this.id = options.id;
    this.productChosenOptionId = options.pc;
    this.colors = options.c;
    this.delta = parseFloat(options.d);
    this.color_type = parseInt(options.ct, 10);
  },
  
  getDelta: function PClr_getDelta(product, color_type) {
    return 0;
  },
  
  getFieldChoice: function PClr_getFieldChoice(product) {
    return null;
  }
});


var CustomProduct = Class.create({
  CLASSDEF: {
      name: 'CustomProduct'
  },
  
  initialize: function CProd_initialize(id, options) {
    this.id = id;
    this.name = options.n;
    this.viewOptions = options.v;
    this.productId = options.p;
    this.markup = options.m;
    this.defaultColor = options.color;
    this.allColors = options.allColors;
    if(!this.allColors) {
      this.colors = options.colors;
    }
    this.defaultProcessId = options.def_proc;
    this.configuredPrice = options.cp;
    this.viewUrls = {}; //this contains a map of strings with urls to the views in /xxx[CID]/[SIZE]/xxx format
    for(var i=0; i < options.u.length;i++) {
      var urlInfo = options.u[i];
      this.viewUrls[urlInfo.id] = urlInfo.u;
    }
  },
  
  //get the view url for the size and color....
  getViewURL: function CProd_getViewURL(viewId, size, colorId, scale) {
    var viewUrl = this.viewUrls[viewId];
    if(viewUrl == null) {
      return null;
    }
    var url = viewUrl.replace(d.layoutViewUrlCID, colorId);
    url = url.replace(d.layoutViewUrlS, size );
    return url.replace(d.layoutViewUrlSc, scale );
  },
  
  getColors: function CProd_getColors() {
    if(this.allColors) {
      return d.productsById[this.productId].colors;
    } else {
      if(this.availableColors == null) {
        var prod = d.productsById[this.productId];
        this.availableColors = new MapList();
        for(var i=0; i < this.colors.length;i++) {
          var c = prod.colors.byId[this.colors[i]];
          if(c != null) {
            this.availableColors.add(c);
          }
        }
      }
      return this.availableColors;
    }
  }
});
