if __name__ == "__main__":
    """ For testing """
    import sys
    sys.path.append("./")

import math
from collections import defaultdict
from datetime import datetime, timedelta
from sqlalchemy.sql import func
from histowiz.models.user import User
from histowiz.models.order import Order
from histowiz.models.slide import Slide, SlideStorageHistory
from histowiz.models.stain import IHCAntigen
from histowiz.controllers.objmgr import ObjectManager
from histowiz.config import STORAGE_CHARGE_START_DATE, RECEIVED_STORAGE, ORDER_PRICE_TABLE
from histowiz import db

class PricingAttributes(object):
    SUB_UNSTAINED = 4
    SUB_PARAFFIN = 6
    SUB_OTHER = 7
    SUB_STAINED_SLIDES = 8
    
    def __init__(self, tat, decalc,
                 is_hsr,
                 is_routine_histology,
                 submitted_in,
                 section_steps,
                 p_and_e_only, whole_slide_imaging,
                 auto_image_analysis,
                 is_pathology,
                 sample_slide_counts, # list of dicts of key: val (count types and counts)
                 ihc_antigen_slide_counts, # list of dicts of {antigen_name: <name>, slide_count: <count>}
                 gen_ship_label,
                 shipping_no,
                 shipping_cost
                 ):
        self.tat = tat
        self.decalc = decalc
        self.is_hsr = is_hsr
        self.is_routine_histology = is_routine_histology
        self.submitted_in = submitted_in
        self.section_steps = int(section_steps)
        self.p_and_e_only = p_and_e_only
        self.whole_slide_imaging = whole_slide_imaging
        self.auto_image_analysis = auto_image_analysis
        self.is_pathology = is_pathology
        self.sample_slide_counts = sample_slide_counts
        self.ihc_antigen_slide_counts = ihc_antigen_slide_counts
        
        self.total_sample_count = len(self.sample_slide_counts)
        
        self.he_sample_count = sum(map(lambda x: (x['he_slide_count'] > 0 and 1) or 0, self.sample_slide_counts))
        self.non_he_sample_count = self.total_sample_count - self.he_sample_count
        
        a = sum(map(lambda x: x['unstained_slide_count'], self.sample_slide_counts))
        b = sum(map(lambda x: x['he_slide_count'], self.sample_slide_counts))
        c = sum(map(lambda x: x['ihc_slide_count'], self.sample_slide_counts))
        d = sum(map(lambda x: x['special_stains_slide_count'], self.sample_slide_counts))

        self.total_unstained_slide_count = a
        self.total_he_slide_count = b
        self.total_ihc_slide_count = c
        self.total_special_slide_count = d

        self.ihc_tunel_slide_count = sum(map(lambda x: ((x[0] == 'TUNEL') and x[1]) or 0, self.ihc_antigen_slide_counts.items()))
        self.ihc_non_tunel_slide_count = self.total_ihc_slide_count - self.ihc_tunel_slide_count
        
        self.total_slide_count = sum([a,b,c,d])
        
        print "SHIPPING STUFF: %s %s %s" % (gen_ship_label, shipping_no, shipping_cost)

        if not not int(gen_ship_label):
            use_histowiz_shipping_no = not (shipping_no and shipping_no.strip())
            
            if use_histowiz_shipping_no and shipping_cost:
                self.include_shipping = True
                self.shipping_cost = shipping_cost
            else:
                self.include_shipping = False
        else:
            self.include_shipping = False

    def is_submitted_unstained(self):
        return self.SUB_UNSTAINED in self.submitted_in
    
    def is_submitted_paraffin(self):
        return self.SUB_PARAFFIN in self.submitted_in
    
    def is_submitted_other(self):
        return self.SUB_OTHER in self.submitted_in
    
    def is_submitted_stained_slides(self):
        return self.SUB_STAINED_SLIDES in self.submitted_in

    def is_submitted_unstained_or_paraffin(self):
        return any([self.is_submitted_unstained(), self.is_submitted_paraffin()])

    def is_submitted_unstained_paraffin_or_other(self):
        return any([self.is_submitted_unstained(), self.is_submitted_paraffin(), self.is_submitted_other()])
    
    @classmethod
    def from_order(self, order):
        ObjectManager.populate_order_details(order)

        is_hsr = hasattr(order, 'histology_request')
        is_rh = order.routine_histology
        submitted_in = order.received_in_id_list

        
        ihc_antigen_slide_count = {}
        for s in order.ihc:
            antigen_name = s.ihc_stain.antigen.name
            if antigen_name not in ihc_antigen_slide_count:
                ihc_antigen_slide_count[antigen_name] = 0
            ihc_antigen_slide_count[antigen_name] += s.slide_count

        pa = PricingAttributes(order.turnaround_time,
                               order.decalcification,
                               is_hsr,
                               is_rh,
                               submitted_in,
                               order.section_steps,
                               is_hsr and order.histology_request.process_and_embed_only,
                               is_hsr and order.histology_request.whole_slide_imaging,
                               is_hsr and order.histology_request.auto_image_analysis,
                               hasattr(order, 'pathology_request'),
                               map(lambda s: {
                                'he_slide_count': s.he_slide_count,
                                'unstained_slide_count': s.unstained_slide_count,
                                'ihc_slide_count': s.ihc_slide_count,
                                'special_stains_slide_count': s.special_stains_slide_count
                                }, order.samples),
                                ihc_antigen_slide_count,
                                order.generate_shipping_label,
                                order.shipping_no,
                                order.shipping_cost
                              )
        return pa
    
    @classmethod
    def from_form(self, form, tdata):
        ihc_antigen_to_slide_count_dict = defaultdict(int)

        sample_slide_counts = map(lambda x: {
            'he_slide_count': x.he_slide_count,
            'unstained_slide_count': x.unstained_slide_count,
            'ihc_slide_count': (hasattr(x, 'ihc_stains') and sum(map(lambda z: z['slide_count'], x.ihc_stains))) or 0,
            'special_stains_slide_count': (hasattr(x, 'sp_stains') and sum(map(lambda z: z['slide_count'], x.sp_stains))) or 0
            },
            tdata['sample-content'].processed_rows)
    
        
        for ihc in tdata['ihc-content'].rows:
            try:
                antigen_name = IHCAntigen.query.get(ihc.antigen_id).name
                ihc_antigen_to_slide_count_dict[antigen_name] += ihc.slide_count
            except:
                pass

        section_steps = (form.section_steps.data and int(form.section_steps.data)) or 0

        pa = PricingAttributes(form.turnaround_time.data,
                               form.decalcification.data,
                               True,
                               form.routine_histology.data,
                               map(lambda x: int(x), form.received_in_id_list.data),
                               section_steps,
                               form.process_and_embed_only.data,
                               form.whole_slide_imaging.data,
                               form.auto_image_analysis.data,
                               form.pathologist_id.data > -1,
                               sample_slide_counts,
                               ihc_antigen_to_slide_count_dict,
                               form.generate_shipping_label.data,
                               form.shipping_no.data,
                               'To be determined'
                               )

        return pa

class PriceCalculator(object):


    BASE_S3_COST_SLIDE_MONTH = 0.14
    BASE_GLACIER_COST_SLIDE_MONTH = 0.02
    PROCESSING_FEE_SLIDE = 0.41
    TO_S3 = 0
    TO_GLACIER = 1
    
    # Fee: $/slide/month multiplier
    #   0 - 100 slides = base-cost x 4
    # 100 - 999 slides = base-cost x 3.5
    # 1000 - 9999 slides = base-cost x 3
    PRICING_MULT_BUCKETS = [
        (0, 4), (100, 3.5), (1000, 3), (10000, 3)
    ]
    
    @classmethod
    def get_service_item_names(cls):
        return map(lambda x: (x['code'], x['label']), ORDER_PRICE_TABLE.values())
    
    @classmethod
    def get_user_billing_report(cls, as_csv_str=False, include_accounting=False):
        report = []
        report_str = ',"User ID","Last Name","First Name","Slide Count","Storage Fee Incurred"\n'
        for u in User.query.order_by(User.last_name, User.first_name):
            pc = PriceCalculator(u.id)
            due = pc.get_amount_due(include_accounting=include_accounting)
            if due['total_slides'] > 0 and due['total_fees'] > 0:
                due['user'] = u
                if (as_csv_str):
                    report_str += 'SUMMARY,%s,"%s","%s",%s,%s\n' % (due['user'].id, due['user'].last_name, due['user'].first_name, due['total_slides'], '${0:.2f}'.format(due['total_fees']))
                    if 'accounting' in due:
                        for s in due['accounting']:
                            report_str += ",SLIDE %s,MULT=%s,RESTORE_MONTHS=%s,TOTAL_SLIDE_COST=%s\n" % (s['slide'],
                                                                                                      s['customer_multiplier'],
                                                                                                      s['total_restore_months'],
                                                                                                      s['total_cost'])
                            for r in s['restore_periods']:
                                report_str += ",,START=%s,END=%s,DAYS=%s\n" % (r['start_date'].strftime("%Y-%m-%d"),
                                                                             r['end_date'].strftime("%Y-%m-%d"),
                                                                             r['days'])
                        report_str += ",SLIDE_DAYS_PER_MONTH=%s\n" % due['total_storage_days_per_month_fmt']
                    """
                        [
                            {
                                'slide': slide.id,
                                'customer_multiplier': customer_multiplier,
                                'restore_periods': {
                                    'start_date': d,
                                    'end_date': e,
                                    'days': days
                                },
                                'total_restore_months': s3_months,
                                'total_cost': round(slide_cost, 2)
                            },
                            ...
                        ]
                    """
                else:
                    report.append(due)
        
        if as_csv_str:
            return report_str
        else:
            return report
    
    def __init__(self, user_id):
        self.now = datetime.now()
        self.user_id = user_id
        self.base_query = Slide.query.join(Slide.order).filter(Order.owner_id == user_id)
    
    @classmethod
    def get_multiplier(cls, total_slides):
        price_mult = 0
        for mult_size, xprice in cls.PRICING_MULT_BUCKETS:
            if total_slides >= mult_size:
                price_mult = xprice
        
        return price_mult
    
    def update_days_by_month(self, days_by_ym, sd, ed):
        delta = ed - sd
        for i in range(delta.days + 1):
            day = sd + timedelta(days=i)
            key = day.strftime("%Y-%m")
            if key not in days_by_ym:
                days_by_ym[key] = 0
            days_by_ym[key] += 1
            
    def format_days_by_month(self, days_by_ym):
        raw_sdays = sorted(map(lambda x: (x[0], x[1]), days_by_ym.items()), key=lambda x: x[0])
        fmt_sdays = map(lambda x: "(%s: %s)" % (x[0], x[1]), raw_sdays)
        
        #days_list = map(lambda x: "(%s: %s)" % (x[0], x[1]), days_by_ym.items())
        #sdays = sorted(days_list)
        
        return {
            'raw': raw_sdays,
            'fmt': ' '.join(fmt_sdays)
        }
        
        return ' '.join(sdays)
    
    def calc_months(self, days_by_ym, slide):
        charge_start_date = datetime.strptime(STORAGE_CHARGE_START_DATE, "%Y-%m-%d %H:%M:%S")
        
        date_list = []
        # date_list.append((max(charge_start_date, slide.created), self.TO_S3))
        
        SSHIST = SlideStorageHistory
        shist = SSHIST.query.filter(SSHIST.slide==slide).order_by(SSHIST.glacier_in_date)
        for rec in shist:
            date_list.append((rec.glacier_in_date, self.TO_S3, None))
            if rec.glacier_out_date:
                date_list.append((rec.glacier_out_date, self.TO_GLACIER, rec.restored_by_admin))
            
        # print "date list: %s" % date_list
        
        restore_periods = []
        s3_days = 0
        
        # Special case, created to first glacier-in-date
        if slide.order.fulfilled_datetime:
            if date_list:
                first_glacier_in = date_list[0][0]
                fulfilled_date = slide.order.fulfilled_datetime
                if first_glacier_in >= charge_start_date:
                    s3_start = max(fulfilled_date, charge_start_date)
                    s3_first_days = (first_glacier_in - s3_start).days
                    if s3_first_days > 30:
                        s3_days += s3_first_days

                        self.update_days_by_month(days_by_ym, s3_start, first_glacier_in)

                        restore_periods.append({
                            'start_date': s3_start,
                            'end_date': first_glacier_in,
                            'days': s3_first_days
                        })
        
        s3_admin_days = 0

        glacier_days = 0
        last_date = None
        last_where = self.TO_GLACIER
        last_was_admin = False
        for d, where, was_admin in date_list:
            if last_date:
                if where == self.TO_S3:
                    if last_date >= charge_start_date:
                        use_date = last_date
                        period_days = (d - use_date).days
                        if last_was_admin:
                            s3_admin_days += period_days
                        else:
                            s3_days += period_days
                        self.update_days_by_month(days_by_ym, use_date, d)
                        restore_periods.append({
                            'start_date': use_date,
                            'end_date': d,
                            'days': period_days,
                            'was_admin': last_was_admin
                        })                    
                elif where == self.TO_GLACIER:
                    glacier_days += (d - last_date).days
                    last_was_admin = not not was_admin
            last_date = d
            last_where = where
            last_was_admin = was_admin
            
        # print "Before last calc: s3_days=%s glacier_days=%s" % (s3_days, glacier_days)

        return (restore_periods, round(s3_days/30.0), round(glacier_days/30.0))

    def calc_slide_amount_due(self, days_by_ym, slide, total_slides):
        (restore_periods, s3_months, glacier_months) = self.calc_months(days_by_ym, slide)
        
        customer_multiplier = PriceCalculator.get_multiplier(total_slides)
        
        if s3_months <= 0 and glacier_months <= 0:
            return {
                'slide': slide.id,
                'customer_multiplier': customer_multiplier,
                'restore_periods': [],
                'total_restore_months': 0,
                'total_cost': 0.0
            }

        #self.PROCESSING_FEE_SLIDE +
        slide_cost = \
            customer_multiplier * (
            s3_months * self.BASE_S3_COST_SLIDE_MONTH + \
            glacier_months * 0.0)
        return {
            'slide': slide.id,
            'customer_multiplier': customer_multiplier,
            'restore_periods': restore_periods,
            'total_restore_months': s3_months,
            'total_cost': round(slide_cost, 2)
        }
    
    def get_slide_count(self):
        return self.base_query.count()
    
    def get_restore_slide_count(self):
        ids = map(lambda x: x.id, self.base_query)
        if ids:
            slide_ids = set(map(lambda x: x.slide_id, SlideStorageHistory.query.filter(SlideStorageHistory.slide_id.in_(ids),
                                                    SlideStorageHistory.glacier_out_date != None)))
            return len(slide_ids)
        else:
            return 0
    
    @classmethod
    def get_itemized_order_price(cls, pa):
        """
        populate_order_details must be called on order object
        before it is passed to this method.
        """
        price_table = ORDER_PRICE_TABLE
        
        def get_cost(key, tat):
            return price_table[key]['cost'][tat]
        
        def get_cost_code(key):
            return price_table[key]['code']
        
        def get_cost_label(key):
            return price_table[key]['label']

        tat = pa.tat

        result = {
            'turnaround_time': pa.tat,
            'items': {},
            'discounts': []
        }

        itemized = result['items']

        if pa.decalc:
            # Bone decalc
            itemized['hw_001'] = {
                'qty': pa.total_sample_count,
                'subtotal': pa.total_sample_count * get_cost('hw_001', tat)
            }

        # Should be anything BUT unstained, stained_slides, paraffin
        if not any([pa.is_submitted_unstained(),
                    pa.is_submitted_stained_slides(),
                    pa.is_submitted_paraffin()
                    ]):
            if pa.total_he_slide_count > 0:
                he_slides = pa.total_he_slide_count - pa.he_sample_count
                
                itemized['hw_006'] = {
                    'qty': he_slides,
                    'subtotal': he_slides * get_cost('hw_006', tat)
                }
                
                itemized['hw_007'] = {
                    'qty': pa.total_sample_count,
                    'subtotal': pa.total_sample_count * get_cost('hw_007', tat)
                }
            else:
                itemized['hw_002'] = {
                    'qty': pa.total_sample_count,
                    'subtotal': pa.total_sample_count * get_cost('hw_002', tat)
                }
        
        if not any([pa.is_submitted_unstained(), pa.is_submitted_stained_slides()]):
            if pa.total_unstained_slide_count > 0:
                itemized['hw_003'] = {
                    'qty': pa.total_unstained_slide_count,
                    'subtotal': pa.total_unstained_slide_count * get_cost('hw_003', tat)
                }
            
        if pa.section_steps and pa.section_steps > 0:
            # Step section surcharge
            itemized['hw_004'] = {
                'qty': (pa.section_steps * pa.total_sample_count),
                'subtotal': pa.section_steps * pa.total_sample_count * get_cost('hw_004', tat),
                'note': "%s steps" % pa.section_steps
            }
            
        if pa.is_submitted_unstained() and pa.is_submitted_paraffin():
            hw_003_slides = 0
            hw_005_slides = 0
            hw_006_slides = 0
            
            for ssc in pa.sample_slide_count:
                he = ssc['he_slide_count']
                un = ssc['unstained_slide_count']
                
                """
                If sample has both unstained and H&E, charge HW-006
                If sample has only H&E, charge HW-005
                If sample has ONLY unstained, HW-003
                """
                if he > 0 and un > 0:
                    hw_006_slides += he
                elif he > 0 and un <= 0:
                    hw_005_slides += he
                elif he <= 0 and un > 0:
                    hw_003_slides += un
            
            if hw_003_slides > 0:
                itemized['hw_003'] = {
                    'qty': hw_003_slides,
                    'subtotal': hw_003_slides * get_cost('hw_003', tat)
                }
            
            if hw_005_slides > 0:
                itemized['hw_005'] = {
                    'qty': hw_005_slides,
                    'subtotal': hw_005_slides * get_cost('hw_005', tat)
                }
            
            if hw_006_slides > 0:
                itemized['hw_006'] = {
                    'qty': hw_006_slides,
                    'subtotal': hw_006_slides * get_cost('hw_006', tat)
                }
        else:
            if pa.is_submitted_unstained() and pa.total_he_slide_count > 0 and pa.total_unstained_slide_count <= 0:
                itemized['hw_005'] = {
                    'qty': pa.total_he_slide_count,
                    'subtotal': pa.total_he_slide_count * get_cost('hw_005', tat)
                }
            
            if pa.is_submitted_paraffin() and pa.total_he_slide_count > 0 and pa.total_unstained_slide_count > 0:
                itemized['hw_006'] = {
                    'qty': pa.total_he_slide_count,
                    'subtotal': pa.total_he_slide_count * get_cost('hw_006', tat)
                }
            
        if not pa.is_submitted_stained_slides():
            if pa.total_special_slide_count > 0:
                itemized['hw_008'] = {
                    'qty': pa.total_special_slide_count,
                    'subtotal': pa.total_special_slide_count * get_cost('hw_008', tat)
                }
                
            if pa.ihc_non_tunel_slide_count > 0:
                itemized['hw_009'] = {
                    'qty': pa.ihc_non_tunel_slide_count,
                    'subtotal': pa.ihc_non_tunel_slide_count * get_cost('hw_009', tat)
                }
                
            if pa.ihc_tunel_slide_count > 0:
                # IHC Tunel
                itemized['hw_010'] = {
                    'qty': pa.ihc_tunel_slide_count,
                    'subtotal': pa.ihc_tunel_slide_count * get_cost('hw_010', tat)
                }
            
        stained_slide_count = pa.total_slide_count - pa.total_unstained_slide_count
        if all([stained_slide_count > 0,
               pa.whole_slide_imaging]):
            itemized['hw_011'] = {
                'qty': stained_slide_count,
                'subtotal': stained_slide_count * get_cost('hw_011', tat)
            }

        if pa.auto_image_analysis:
            itemized['hw_012'] = {
                'qty': pa.total_slide_count,
                'subtotal': pa.total_slide_count * get_cost('hw_012', tat)
            }

        if pa.is_pathology:
            # Pathology
            itemized['hw_013'] = {
                'qty': pa.total_slide_count,
                'subtotal': pa.total_slide_count * get_cost('hw_013', tat)
            }
            
        if pa.total_slide_count <= 25:
            itemized['hw_014'] = {
                'qty': 1,
                'subtotal': get_cost('hw_014', tat)
            }
        elif pa.total_slide_count > 25:
            if pa.total_slide_count <= 100:
                itemized['hw_015'] = {
                    'qty': 1,
                    'subtotal': get_cost('hw_015', tat)
                }
            else:
                box_count = math.ceil(pa.total_slide_count / 100.0)
                itemized['hw_015'] = {
                    'qty': box_count,
                    'subtotal': box_count * get_cost('hw_015', tat)
                }
        
        # Convert to list
        item_list = []
        for key, val in itemized.items():
            valcpy = dict(val)
            valcpy['name'] = key
            valcpy['unit_price'] = get_cost(key, tat)
            valcpy['unit_type'] = ORDER_PRICE_TABLE[key]['unit_type']
            valcpy['code'] = ORDER_PRICE_TABLE[key]['code']
            valcpy['label'] = ORDER_PRICE_TABLE[key]['label']
            item_list.append(valcpy)

        result['items'] = item_list
        result['items'] = filter(lambda x: x['subtotal'] > 0, result['items'])
            
        if pa.include_shipping:
            base_ship_item = {
                'name': 'hw_ship',
                'qty': 'n/a',
                'subtotal': pa.shipping_cost,
                'unit_price': 'n/a',
                'unit_type': 'n/a',
                'code': ORDER_PRICE_TABLE['hw_ship']['code'],
                'label': 'Sample shipping'
            }
            
            if isinstance(pa.shipping_cost, basestring):
                result['items_total'] = sum(map(lambda x: x['subtotal'], result['items']))
                result['items'].append(base_ship_item)

            else:
                result['items'].append(base_ship_item)
                result['items_total'] = sum(map(lambda x: x['subtotal'], result['items']))
        else:
            result['items_total'] = sum(map(lambda x: x['subtotal'], result['items']))

        
        if pa.total_slide_count >= 100:
            result['discounts'].append(
                {'name': 'Discount',
                 'code': 'Discount',
                 'unit_type': '10% off',
                 'qty': 1,
                 'subtotal': - 0.10 * result['items_total'],
                 'unit_price': 'N/A',
                 'label': '10% off volume discount'
                })

        # Apply discounts
        result['total'] = result['items_total']
        for d in result['discounts']:
            result['total'] += d['subtotal']

        return result
    
    @classmethod
    def get_itemized_preview(cls, pa):
        """
        Provide a simple list of line items explaining the order price
        Mostly used internally, we'll need to convert base itemized price into QuickBooks format
        
        Return list of lists: rows / columns as follows:
        Item name | Unit Price | Unit Type | Quantity | Subtotal
        ...
        Total: 
        """

        result = cls.get_itemized_order_price(pa)
        tat = pa.tat
        
        final = []
        
        def numeric_price(price):
            return ((not isinstance(price, basestring)) and "${0:.2f}".format(price)) or price        
        
        total = result['total']
        items = result['items']
        for item in items:
            qty = item['qty']
            sub = item['subtotal']
            key = item['name']
            unit_type = ORDER_PRICE_TABLE[key]['unit_type']
            unit_price = ORDER_PRICE_TABLE[key]['cost'][tat]
            code = ORDER_PRICE_TABLE[key]['code']
            label = ORDER_PRICE_TABLE[key]['label']

            row = [code, label,
                   numeric_price(unit_price),
                   unit_type, qty,
                   numeric_price(sub)]
            final.append(row)
        
        for item in result['discounts']:
            qty = item['qty']
            sub = item['subtotal']
            key = item['name']
            unit_type = item['unit_type']
            unit_price = item['unit_price']
            code = item['code']
            label = item['label']
            
            row = [code, label, unit_price, unit_type, qty, "${0:.2f}".format(sub)]
            final.append(row)

        return (final, "${0:.2f}".format(total))

    def get_amount_due(self, include_accounting=False):
        """
        Calculate the amount due for a given user's slides.
        If paid_through_date is provided, deduct any fees incurred
        prior to that date
        """
        
        sess = db.session
        
        
        # Get oldest slide date
        slide_min_date = None
        slide_min_date_res = list(sess.query(func.min(Slide.created)).join(Slide.order).filter(Order.owner_id == self.user_id))
        if slide_min_date_res:
            slide_min_date = slide_min_date_res[0][0]

        slide_count = self.get_slide_count()
        
        restore_slide_count = self.get_restore_slide_count()
        
        days_by_ym = {}
        
        slide_accounting = filter(lambda x: x['total_cost'] > 0, map(lambda slide: self.calc_slide_amount_due(days_by_ym, slide, slide_count), self.base_query))
        
        ### slide_accounting
        """
        [
            {
                'slide': slide.id,
                'customer_multiplier': customer_multiplier,
                'restore_periods': {
                    'start_date': d,
                    'end_date': e,
                    'days': days
                },
                'total_restore_months': s3_months,
                'total_cost': round(slide_cost, 2)
            },
            ...
        ]
        """
        
        #pprint.pprint(sorted(slide_prices, key=lambda x: x[0]))
        
        user = User.query.get(self.user_id)
        
        paid_to_date = float(user.aws_payment_to_date)
        
        #print "slide_accounting: %s" % slide_accounting
        total_price = sum(map(lambda x: x['total_cost'], slide_accounting))
        #print "Total amount due for user %s, %s: %s" % (user.last_name, user.first_name, "{0:.2f}".format(total_price))

        #print "slide count: %s" % slide_count
        
        # per_slide_price = 

        # Calculate how long each slide has been in the system in months
        # For a given user
        # Check which pricing bucket they fall into: 100, 1000, 10000
        
        if slide_min_date:
            slide_min_date_fmt = slide_min_date.strftime("%Y-%m-%d")
        else:
            slide_min_date_fmt = "Unknown"
            
        storage_days = self.format_days_by_month(days_by_ym)

        result = {
            'start_date': slide_min_date_fmt,
            'total_slides': slide_count,
            'restored_slides': restore_slide_count,
            'total_fees': total_price,
            'total_storage_days_per_month_fmt': storage_days['fmt'],
            'total_storage_days_per_month_raw': storage_days['raw'],
            'balance_due': total_price-paid_to_date
        }
        
        if include_accounting:
            result['accounting'] = slide_accounting
        
        return result
    
    def get_day_count_in_month(self, year, month, start_date, end_date):
        delta = end_date - start_date
        day_count = 0
        for i in range(delta.days + 1):
            day = start_date + timedelta(days=i)
            if day.year == year and day.month == month:
                day_count += 1
        return day_count
    
    def get_monthly_storage_itemized(self, year, month):
        if month < 10:
            mon = "0%s" % month
        else:
            mon = str(month)
        ym = "%s-%s" % (year, mon)
        
        unit_price = None
        
        cust_multiplier = 0
        amt_due = self.get_amount_due(include_accounting=True)
        monthly_dict = dict(amt_due['total_storage_days_per_month_raw'])
        if ym in monthly_dict:
            # {<slide_id>: {'days': <days>, 'cost': <cost>}}
            result_items = {}
            
            # Calculate pro-rated cost per month for the current month
            for item in amt_due['accounting']:
                if cust_multiplier <= 0:
                    cust_multiplier = item['customer_multiplier']
                if unit_price is None:
                    unit_price = round(float(cust_multiplier) * self.BASE_S3_COST_SLIDE_MONTH / 30.0, 4)
                
                slide_id = str(item['slide'])
                if slide_id not in result_items:
                    result_items[slide_id] = {'days': 0, 'cost': 0.0}
                
                periods = item['restore_periods']
                for p in periods:
                    slide_day_count = self.get_day_count_in_month(year, month, p['start_date'], p['end_date'])
                    result_items[slide_id]['days'] += slide_day_count
                    
                result_items[slide_id]['cost'] = round(float(result_items[slide_id]['days']) * unit_price, 2)

            slide_days_dict = {
                'ym': ym,
                'unit_price': unit_price,
                'items': result_items
            }

            return slide_days_dict
        else:
            return None
    
if __name__ == "__main__":
    import pprint
    pc = PriceCalculator
    from histowiz.controllers.objmgr import ObjectManager
    orders = Order.query.filter(Order.id == 945) #(Order.id > 500)
    first_20 = []
    for o in orders:
        print "====== ORDER %s ======" % o.id
        if len(first_20) <= 19:
            pa = PricingAttributes.from_order(o)
            pprint.pprint(pc.get_itemized_preview(pa))
            #first_20.append(o)
        else:
            break
