public ReprocessJob(long stationID, int groupID, long ownerID)
 {
     _id = 0;
     _date = DateTime.UtcNow;
     _stationID = stationID;
     _reportGroupID = groupID;
     _ownerID = ownerID;
     _items = new ReprocessItemList();
     _gotItems = true;
     _results = new ReprocessResultList();
     _gotResults = true;
 }
        /// <summary>
        /// Get a list of reprocess results that produced the specified item for the 
        /// specified report group.
        /// </summary>
        /// <param name="itemID"></param>
        /// <param name="reportGroupID"></param>
        /// <returns></returns>
        public static ReprocessResultList GetItemResults(int itemID, int reportGroupID)
        {
            ReprocessResultList retVal = new ReprocessResultList();

            EMMADataSet.ReprocessResultDataTable table = new EMMADataSet.ReprocessResultDataTable();
            lock (resultTableAdapter)
            {
                resultTableAdapter.FillByGroupAndItem(table, itemID, reportGroupID);
            }

            foreach (EMMADataSet.ReprocessResultRow row in table)
            {
                retVal.Add(new ReprocessResult(row));
            }

            return retVal;
        }
        /// <summary>
        /// Get the results of a specific reprocessing job.
        /// </summary>
        /// <param name="jobID"></param>
        /// <returns></returns>
        public static ReprocessResultList GetJobResults(int jobID)
        {
            ReprocessResultList retVal = new ReprocessResultList();

            EMMADataSet.ReprocessResultDataTable table = new EMMADataSet.ReprocessResultDataTable();
            lock (resultTableAdapter)
            {
                resultTableAdapter.FillByJob(table, jobID);
            }

            foreach (EMMADataSet.ReprocessResultRow row in table)
            {
                retVal.Add(new ReprocessResult(row));
            }

            return retVal;
        }
        private static void GetItemTransData(List<FinanceAccessParams> accessParams, List<int> itemIDs,
            List<long> regionIDs, List<long> stationIDs, DateTime startDate, DateTime endDate,
            long quantity, long recentBuyUnitsToIgnore,
            ref decimal avgSellPrice, ref decimal medianSellPrice, ref decimal avgBuyPrice,
            ref decimal medianBuyPrice, ref long unitsBought, ref long unitsSold,
            ref decimal brokerBuyFees, ref decimal brokerSellFees, ref decimal transactionTax,
            ref decimal transportCosts, ref decimal avgSellProfit,
            bool calcBrokerFees, bool calcTransTax, bool getBuyData, bool getSellData, bool getMedians,
            bool calcTransportCosts, bool useReprocessData, bool useMostRecentBuyPrice,
            bool restrictedCostCalc)
        {
            long totBuy = 0, totSell = 0;
            decimal totIskBuy = 0, totIskSell = 0, totIskSellProfit = 0;
            brokerBuyFees = 0;
            brokerSellFees = 0;
            transactionTax = 0;
            transportCosts = 0;
            // Used for working out transport costs for sell transactions.
            Dictionary<long, Dictionary<int, long>> quantities = new Dictionary<long, Dictionary<int, long>>();
            long quantityRemaining = quantity;
            bool ignoreQuantity = quantity == 0;
            // These map from a char ID to a value from 1-5 that is the character's skill level in that skill.
            Dictionary<long, int> brokerRelations = new Dictionary<long, int>();
            Dictionary<long, int> accounting = new Dictionary<long, int>();

            SortedList<decimal, long> priceFrequencies = new SortedList<decimal, long>();

            startDate = startDate.ToUniversalTime();
            endDate = endDate.ToUniversalTime();

            Diagnostics.ResetTimer("Transactions.GetBuyTrans");
            Diagnostics.ResetTimer("Transactions.ProcessBuyTrans");
            Diagnostics.ResetTimer("Transactions.CalcBuyBrokerFees");
            Diagnostics.ResetTimer("Transactions.GetBkrRelLvl");
            Diagnostics.ResetTimer("Transactions.CalcBuyBrokerFeesGetOrder");
            Diagnostics.ResetTimer("Transactions.GetStanding");
            Diagnostics.ResetTimer("Transactions.CalculateBuyBkr");
            Diagnostics.ResetTimer("Transactions.CalcBuyMedian");
            Diagnostics.ResetTimer("Transactions.GetSellTrans");
            Diagnostics.ResetTimer("Transactions.ProcessSellTrans");
            Diagnostics.ResetTimer("Transactions.CalcSellBrokerFees");
            Diagnostics.ResetTimer("Transactions.CalcSellTransTax");
            Diagnostics.ResetTimer("Transactions.CalcSellTransportCosts");
            Diagnostics.ResetTimer("Transactions.CalcSellMedian");

            EMMADataSet.TransactionsDataTable transactions = new EMMADataSet.TransactionsDataTable();
            if (getBuyData)
            {
                Diagnostics.StartTimer("Transactions.GetBuyTrans");
                // Retrieve buy transactions that match our criteria
                transactions = GetTransData(accessParams, itemIDs, regionIDs, stationIDs, startDate, endDate, "Buy");
                transactions.OrderByDescending(t => t.DateTime);
                Diagnostics.StopTimer("Transactions.GetBuyTrans");
                ReprocessResultList reprocessResults = new ReprocessResultList();
                if (itemIDs.Count == 1 && useReprocessData)
                {
                    reprocessResults = ReprocessJobs.GetItemResults(itemIDs[0], UserAccount.CurrentGroup.ID);
                }

                priceFrequencies = new SortedList<decimal, long>();

                Diagnostics.StartTimer("Transactions.ProcessBuyTrans");
                int transIndex = -1;
                int reprocIndex = (reprocessResults.Count == 0 ? -2 : -1);
                bool useReproc = false;
                bool useTrans = true;
                for (int i = 0; i < transactions.Count + reprocessResults.Count; i++)
                {
                    decimal currentUnitPrice = 0.0m;
                    long currentQuantity = 0;

                    if (reprocIndex != -2)
                    {
                        DateTime nextTransDate = DateTime.MinValue;
                        DateTime nextReprocDate = DateTime.MinValue;
                        if (transactions.Count > transIndex + 1)
                        {
                            nextTransDate = transactions[transIndex + 1].DateTime;
                        }
                        if (reprocessResults.Count > reprocIndex + 1)
                        {
                            nextReprocDate = reprocessResults[reprocIndex + 1].JobDate;
                        }
                        if (nextTransDate.CompareTo(nextReprocDate) < 0)
                        {
                            useReproc = true;
                            useTrans = false;
                            reprocIndex++;
                        }
                        else
                        {
                            useReproc = false;
                            useTrans = true;
                            transIndex++;
                        }
                    }
                    else
                    {
                        transIndex++;
                    }

                    bool includeTrans = true;
                    if (useTrans)
                    {
                        EMMADataSet.TransactionsRow trans = transactions[transIndex];
                        currentQuantity = trans.Quantity;
                        currentUnitPrice = trans.Price;
                    }
                    else if (useReproc)
                    {
                        ReprocessResult result = reprocessResults[reprocIndex];
                        currentQuantity = result.Quantity;
                        currentUnitPrice = result.EffectiveBuyPrice / currentQuantity;
                    }

                    if (recentBuyUnitsToIgnore > 0)
                    {
                        // If we're ignoring the first x units then first reduce the quantity we have
                        // to ignore by the quantity of the current transaction
                        int quantityToUse = (int)(recentBuyUnitsToIgnore < currentQuantity ?
                            recentBuyUnitsToIgnore : currentQuantity);
                        recentBuyUnitsToIgnore -= quantityToUse;
                        if (recentBuyUnitsToIgnore == 0)
                        {
                            // If the current transaction has a greater quantity than we are ignoring
                            // then reduce the quantity on the transaction by whatever we have left to ignore
                            currentQuantity -= quantityToUse;
                        }
                        else
                        {
                            // otherwise, just move to the next transaction.
                            includeTrans = false;
                        }
                    }

                    if (includeTrans)
                    {
                        int quantityToUse = 1;

                        if (quantityRemaining > 0 || ignoreQuantity)
                        {
                            quantityToUse = (int)(ignoreQuantity ? currentQuantity :
                                (quantityRemaining < currentQuantity ? quantityRemaining : currentQuantity));

                            // Increase total buy units and total isk on buy transactions by
                            // the appropriate amounts.
                            totBuy += quantityToUse;
                            decimal transTot = currentUnitPrice * quantityToUse;
                            totIskBuy += transTot;

                            if (getMedians)
                            {
                                if (priceFrequencies.ContainsKey(currentUnitPrice))
                                {
                                    priceFrequencies[currentUnitPrice] = priceFrequencies[currentUnitPrice] + quantityToUse;
                                }
                                else
                                {
                                    priceFrequencies.Add(currentUnitPrice, quantityToUse);
                                }
                            }

                            #region Calculate broker fees
                            if (calcBrokerFees && useTrans)
                            {
                                Diagnostics.StartTimer("Transactions.CalcBuyBrokerFees");
                                Order buyOrder = null, empty = null;

                                Diagnostics.StartTimer("Transactions.CalcBuyBrokerFeesGetOrder");
                                EMMADataSet.TransactionsRow trans = transactions[transIndex];
                                Orders.GetOrder(new Transaction(trans), out buyOrder, out empty);
                                Diagnostics.StopTimer("Transactions.CalcBuyBrokerFeesGetOrder");
                                if (buyOrder != null)
                                {
                                    Diagnostics.StartTimer("Transactions.GetBkrRelLvl");
                                    long id = trans.BuyerForCorp ? trans.BuyerCharacterID : trans.BuyerID;
                                    int bkrrellvl = 0;
                                    decimal corpStanding = 0;
                                    decimal factionStanding = 0;

                                    if (brokerRelations.ContainsKey(id))
                                    {
                                        bkrrellvl = brokerRelations[id];
                                    }
                                    else
                                    {
                                        bool corpID = false;
                                        bkrrellvl = UserAccount.CurrentGroup.GetCharacter(id, ref corpID).BrokerRelationsLvl;
                                        brokerRelations.Add(id, bkrrellvl);
                                    }
                                    Diagnostics.StopTimer("Transactions.GetBkrRelLvl");

                                    Diagnostics.StartTimer("Transactions.GetStanding");
                                    EveDataSet.staStationsRow station = Stations.GetStation(buyOrder.StationID);
                                    if (station != null && !station.IscorporationIDNull())
                                    {
                                        int stationCorp = station.corporationID;
                                        EveDataSet.crpNPCCorporationsRow npcCorp =
                                            NPCCorps.GetCorp(stationCorp);
                                        if (npcCorp != null)
                                        {
                                            factionStanding = Standings.GetStanding(trans.BuyerID,
                                                npcCorp.factionID);
                                        }
                                        corpStanding = Standings.GetStanding(trans.BuyerID, stationCorp);
                                    }
                                    Diagnostics.StopTimer("Transactions.GetStanding");

                                    Diagnostics.StartTimer("Transactions.CalculateBuyBkr");
                                    decimal fee = transTot * (decimal)(1 /
                                        Math.Exp((double)(0.1m * factionStanding + 0.04m * corpStanding)) *
                                        1 - (0.05 * bkrrellvl)) / 100.0m;
                                    Diagnostics.StopTimer("Transactions.CalculateBuyBkr");

                                    brokerBuyFees += fee;
                                }

                                Diagnostics.StopTimer("Transactions.CalcBuyBrokerFees");
                            }
                            #endregion
                        }
                        else
                        {
                            // if we're only retrieving data for x units and have already got that
                            // many then just jump out of the loop.
                            i = transactions.Count + reprocessResults.Count;
                        }

                        quantityRemaining -= quantityToUse;
                    }
                }
                Diagnostics.StopTimer("Transactions.ProcessBuyTrans");

                #region Calculate median buy price
                if (getMedians && priceFrequencies.Count > 0)
                {
                    Diagnostics.StartTimer("Transactions.CalcBuyMedian");

                    float tmp = (totBuy + 1) / 2;
                    long limit = (long)Math.Round(tmp, MidpointRounding.AwayFromZero);
                    if (limit != tmp) { limit -= 1; }
                    int index = 0;
                    long quantitySoFar = 0;

                    while (quantitySoFar < limit)
                    {
                        quantitySoFar += priceFrequencies[priceFrequencies.Keys[index]];
                        index++;
                    }

                    if (limit == 0)
                    {
                        medianSellPrice = priceFrequencies.Keys[0];
                    }
                    else if (quantitySoFar > limit || limit == tmp)
                    {
                        medianBuyPrice = priceFrequencies.Keys[index - 1];
                    }
                    else
                    {
                        medianBuyPrice = (priceFrequencies.Keys[index - 1] + priceFrequencies[index]) / 2;
                    }

                    /*decimal[] priceArray = prices.ToArray();
                    Array.Sort<decimal>(priceArray);

                    int rem = 0;
                    int result = Math.DivRem(priceArray.Length, 2, out rem);
                    decimal median = 0;
                    if (rem == 0)
                    {
                        median = (priceArray[result - 1] + priceArray[result]) / 2;
                    }
                    else
                    {
                        median = priceArray[result];
                    }
                    medianBuyPrice = median;*/
                    Diagnostics.StopTimer("Transactions.CalcBuyMedian");
                }
                #endregion
            }

            if (getSellData)
            {
                Diagnostics.StartTimer("Transactions.GetSellTrans");
                // Retrieve sell transactions that match our criteria.
                transactions = GetTransData(accessParams, itemIDs, regionIDs, stationIDs, startDate, endDate, "Sell");
                Diagnostics.StopTimer("Transactions.GetSellTrans");

                priceFrequencies = new SortedList<decimal, long>();

                quantityRemaining = quantity;
                Diagnostics.StartTimer("Transactions.ProcessSellTrans");
                for (int i = 0; i < transactions.Count; i++)
                {
                    EMMADataSet.TransactionsRow trans = transactions[i];
                    int quantityToUse = 1;

                    if (quantityRemaining > 0 || ignoreQuantity)
                    {
                        quantityToUse = (int)(ignoreQuantity ? trans.Quantity :
                            (quantityRemaining < trans.Quantity ? quantityRemaining : trans.Quantity));

                        // Increase total sell units and total isk on sell transactions by
                        // the appropriate amounts.
                        totSell += quantityToUse;
                        decimal transTot = trans.Price * quantityToUse;
                        totIskSell += transTot;
                        if (trans.SellerUnitProfit == 0)
                        {
                            // If the seller unit profit has not been recorded for some reason then
                            // we need to calculate it.
                            Transaction t = new Transaction(trans);
                            totIskSellProfit += t.GrossUnitProfit * quantityToUse;
                        }
                        else { totIskSellProfit += trans.SellerUnitProfit * quantityToUse; }

                        if (getMedians)
                        {
                            decimal price = trans.Price;
                            if (priceFrequencies.ContainsKey(price))
                            {
                                priceFrequencies[price] = priceFrequencies[price] + quantityToUse;
                            }
                            else
                            {
                                priceFrequencies.Add(price, quantityToUse);
                            }
                        }

                        #region Calculate broker fees
                        if (calcBrokerFees)
                        {
                            // Only add the broker fee if we've got a matching journal record.
                            // If we don't it was probably a quick buy (i.e. no broker fee)
                            Order sellOrder = null, blank = null;
                            Orders.GetOrder(new Transaction(trans), out blank, out sellOrder);
                            if (sellOrder != null)
                            {
                                Diagnostics.StartTimer("Transactions.CalcSellBrokerFees");
                                long id = trans.SellerForCorp ? trans.SellerCharacterID : trans.SellerID;
                                int bkrrellvl = 0;
                                decimal factionStanding = 0, corpStanding = 0;

                                if (brokerRelations.ContainsKey(id))
                                {
                                    bkrrellvl = brokerRelations[id];
                                }
                                else
                                {
                                    bool corpID = false;
                                    bkrrellvl = UserAccount.CurrentGroup.GetCharacter(id, ref corpID).BrokerRelationsLvl;
                                    brokerRelations.Add(id, bkrrellvl);
                                }

                                EveDataSet.staStationsRow station = Stations.GetStation(trans.StationID);
                                if (station != null && !station.IscorporationIDNull())
                                {
                                    int stationCorp = station.corporationID;
                                    EveDataSet.crpNPCCorporationsRow npcCorp =
                                        NPCCorps.GetCorp(stationCorp);
                                    if (npcCorp != null)
                                    {
                                        factionStanding = Standings.GetStanding(trans.BuyerID,
                                            npcCorp.factionID);
                                    }
                                    corpStanding = Standings.GetStanding(trans.SellerID, stationCorp);
                                }

                                decimal fee = transTot * (decimal)(1 /
                                    Math.Exp((double)(0.1m * factionStanding + 0.04m * corpStanding)) *
                                    1 - (0.05 * bkrrellvl)) / 100.0m;

                                brokerSellFees += fee;
                            }
                            Diagnostics.StopTimer("Transactions.CalcSellBrokerFees");
                        }
                        #endregion
                        #region Calculate transaction tax
                        if (calcTransTax)
                        {
                            Diagnostics.StartTimer("Transactions.CalcSellTransTax");
                            long id = trans.SellerForCorp ? trans.SellerCharacterID : trans.SellerID;
                            int acclvl = 0;
                            if (accounting.ContainsKey(id))
                            {
                                acclvl = accounting[id];
                            }
                            else
                            {
                                bool corpID = false;
                                acclvl = UserAccount.CurrentGroup.GetCharacter(id, ref corpID).AccountingLvl;
                                accounting.Add(id, acclvl);
                            }
                            transactionTax += transTot * (decimal)(0.01 - 0.001 * acclvl);
                            Diagnostics.StopTimer("Transactions.CalcSellTransTax");
                        }
                        #endregion
                        #region Record quantities for working out transport costs.
                        Dictionary<int, long> itemQuantities;
                        if (quantities.ContainsKey(trans.StationID))
                        {
                            itemQuantities = quantities[trans.StationID];
                        }
                        else
                        {
                            itemQuantities = new Dictionary<int, long>();
                            quantities.Add(trans.StationID, itemQuantities);
                        }

                        if (itemQuantities.ContainsKey(trans.ItemID))
                        {
                            long qSoFar = itemQuantities[trans.ItemID];
                            qSoFar += trans.Quantity;
                            itemQuantities[trans.ItemID] = qSoFar;
                        }
                        else
                        {
                            itemQuantities.Add(trans.ItemID, trans.Quantity);
                        }
                        #endregion
                    }
                    else
                    {
                        // if we're only retrieving data for x units and have already got that
                        // many then just jump out of the loop.
                        i = transactions.Count;
                    }

                    quantityRemaining -= quantityToUse;
                }

                if ((useMostRecentBuyPrice || restrictedCostCalc) && totSell > 0)
                {
                    decimal totIsk = 0;
                    long q = 0;
                    transactions = GetTransData(accessParams, itemIDs, regionIDs, stationIDs, startDate, endDate, "Buy");
                    OrderedEnumerableRowCollection<EMMADataSet.TransactionsRow> orderedTransactions = null;
                    if (useMostRecentBuyPrice)
                    {
                        orderedTransactions = transactions.OrderByDescending(t => t.DateTime);
                    }
                    else if (restrictedCostCalc)
                    {
                        orderedTransactions = transactions.OrderBy(t => t.DateTime);
                    }

                    foreach(EMMADataSet.TransactionsRow trans in orderedTransactions)
                    {
                        //EMMADataSet.TransactionsRow trans = transactions[i];
                        long qToUse = (q + trans.Quantity > totSell) ? (totSell - q) : trans.Quantity;
                        q += qToUse;
                        totIsk += trans.Price * qToUse;
                        if (q >= totSell)
                        {
                            break;
                        }
                    }
                    if (q > 0)
                    {
                        totIskSellProfit = ((totIskSell / totSell) - (totIsk / q)) * totSell;
                    }
                }
                Diagnostics.StopTimer("Transactions.ProcessSellTrans");

            }

            #region Calculate transport costs
            if (calcTransportCosts)
            {
                Diagnostics.StartTimer("Transactions.CalcSellTransportCosts");
                Dictionary<long, Dictionary<int, long>>.Enumerator enumerator = quantities.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    Dictionary<int, long>.Enumerator enumerator2 = enumerator.Current.Value.GetEnumerator();
                    while (enumerator2.MoveNext())
                    {
                        // Get cost of contracts using this item occuring up to 14 days before
                        // the beginning of the report.
                        transportCosts += Contracts.GetTransportCosts(enumerator2.Current.Key,
                            enumerator.Current.Key, enumerator2.Current.Value, startDate.AddDays(-14));
                    }
                }
                Diagnostics.StopTimer("Transactions.CalcSellTransportCosts");
            }
            #endregion
            #region Calculate median sell price
            if (getMedians && priceFrequencies.Count > 0)
            {
                Diagnostics.StartTimer("Transactions.CalcSellMedian");

                float tmp = (totSell + 1) / 2;
                long limit = (long)Math.Round(tmp, MidpointRounding.AwayFromZero);
                if (limit != tmp) { limit -= 1; }
                int index = 0;
                long quantitySoFar = 0;

                while (quantitySoFar < limit)
                {
                    quantitySoFar += priceFrequencies[priceFrequencies.Keys[index]];
                    index++;
                }

                if (limit == 0)
                {
                    medianSellPrice = priceFrequencies.Keys[0];
                }
                else if (quantitySoFar > limit || limit == tmp)
                {
                    medianSellPrice = priceFrequencies.Keys[index - 1];
                }
                else
                {
                    medianSellPrice = (priceFrequencies.Keys[index - 1] + priceFrequencies[index]) / 2;
                }

                /*decimal[] priceArray = prices.ToArray();
                Array.Sort<decimal>(priceArray);

                int rem = 0;
                int result = Math.DivRem(priceArray.Length, 2, out rem);
                decimal median = 0;
                if (rem == 0)
                {
                    median = (priceArray[result - 1] + priceArray[result]) / 2;
                }
                else
                {
                    median = priceArray[result];
                }
                medianSellPrice = median;*/
                Diagnostics.StopTimer("Transactions.CalcSellMedian");
            }
            #endregion

            // Set values for return parameters. (note, broker fees and transaction tax are already set for us.)
            avgSellPrice = (totSell == 0 ? 0 : totIskSell / totSell);
            avgBuyPrice = (totBuy == 0 ? 0 : totIskBuy / totBuy);
            avgSellProfit = (totSell == 0 ? 0 : totIskSellProfit / totSell);
            unitsBought = totBuy;
            unitsSold = totSell;
        }
        public void UpdateResults()
        {
            foreach (ReprocessResult result in _results)
            {
                if (_defaultResultPrices.ContainsKey(result.ItemID))
                {
                    _defaultResultPrices[result.ItemID] = result.UnitSellPrice;
                }
                else
                {
                    _defaultResultPrices.Add(result.ItemID, result.UnitSellPrice);
                }
            }
            _results = new ReprocessResultList();
            _resultIDs.Clear();
            foreach (ReprocessItem item in _items)
            {
                Dictionary<int, double> results = Items.GetItemMaxReprocessResults(item.ItemID);
                Dictionary<int, double>.Enumerator enumerator = results.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    AddResult(enumerator.Current.Key,
                        (long)(Math.Round(enumerator.Current.Value * item.Quantity)));
                }
            }

            _gotResultsValue = false;
            SetEffectiveBuyPrices();
        }