/// <summary>
        /// This is used for calculating per unit profit when adding sell transactions from the API. 
        /// It should not be used outside of that context.
        /// </summary>
        /// <param name="transData"></param>
        /// <param name="newRow"></param>
        /// <returns></returns>
        public static decimal CalcProfit(long ownerID, EMMADataSet.TransactionsDataTable transData,
            EMMADataSet.TransactionsRow newRow, DateTime assetsEffectiveDate)
        {
            decimal retVal = 0;
            EMMADataSet.AssetsDataTable existingAssets = new EMMADataSet.AssetsDataTable();
            long stationID = newRow.StationID;

            //int ownerID = corp ? UserAccount.CurrentGroup.GetCharacter(charID).CorpID : charID;
            bool exDiag = UserAccount.Settings.ExtendedDiagnostics;

            StringBuilder process = new StringBuilder("");

            try
            {
                if (assetsEffectiveDate.CompareTo(newRow.DateTime) > 0)
                {
                    // If assets have been updated from the API after this transaction
                    // occured then the assets are already gone and we cannot work out
                    // the profit.

                    // Note: in future, we could try and work it out from 'AssetsLost'
                    if (exDiag)
                    {
                        process.Append("Effective date of last assets update is after this transaction's date/time.");
                        process.Append(assetsEffectiveDate);
                        process.Append(" / ");
                        process.Append(newRow.DateTime);
                    }
                }
                else
                {

                    // If there are matching assets for the specified character or corp at the
                    // transaction location then use the cost of those assets to calculate profit.
                    List<AssetAccessParams> assetAccessParams = new List<AssetAccessParams>();
                    assetAccessParams.Add(new AssetAccessParams(ownerID));
                    Assets.GetAssets(existingAssets, assetAccessParams, stationID,
                        Stations.GetStation(stationID).solarSystemID, newRow.ItemID);
                    if (existingAssets != null)
                    {
                        decimal totalBuyPrice = 0;
                        long qToFind = newRow.Quantity;
                        foreach (EMMADataSet.AssetsRow existingAsset in existingAssets)
                        {
                            // If possible, use data from assets that are currently for sale via the market.
                            if (existingAsset.Status == (int)AssetStatus.States.ForSaleViaMarket &&
                                existingAsset.Quantity > 0)
                            {
                                Asset asset = new Asset(existingAsset, null);
                                long q = Math.Min(qToFind, asset.Quantity);
                                qToFind -= q;
                                totalBuyPrice += asset.UnitBuyPrice * q;

                                // Adjust assets data in accordance with items that were sold.
                                long deltaQuantity = -1 * q;
                                // Note, since we're removing assets, the cost and costcalc parameters
                                // will be ignored.
                                Assets.ChangeAssets(ownerID, newRow.StationID, newRow.ItemID,
                                    existingAsset.ContainerID, existingAsset.Status, existingAsset.AutoConExclude,
                                    deltaQuantity, 0, false);
                            }
                        }
                        // If we could not find enough assets 'ForSaleViaMarket' to match the
                        // sell transaction then look at assets that are in transit or just sat
                        // in the hanger.
                        // (Don't use assets that are containers!)
                        if (qToFind > 0)
                        {
                            foreach (EMMADataSet.AssetsRow existingAsset in existingAssets)
                            {
                                if (existingAsset.Status != (int)AssetStatus.States.ForSaleViaMarket &&
                                    existingAsset.Status != (int)AssetStatus.States.ForSaleViaContract &&
                                    existingAsset.Quantity > 0 &&
                                    !existingAsset.IsContainer)
                                {
                                    Asset asset = new Asset(existingAsset, null);
                                    long q = Math.Min(qToFind, asset.Quantity);
                                    qToFind -= q;
                                    totalBuyPrice += asset.UnitBuyPrice * q;

                                    // Adjust assets data in accordance with items that were sold.
                                    long deltaQuantity = -1 * q;
                                    // Note, since we're removing assets, the cost and costcalc parameters
                                    // will be ignored.
                                    Assets.ChangeAssets(ownerID, newRow.StationID, newRow.ItemID,
                                        existingAsset.ContainerID, existingAsset.Status, existingAsset.AutoConExclude,
                                        deltaQuantity, 0, false);
                                }
                            }
                        }
                        if (qToFind < newRow.Quantity)
                        {
                            decimal unitBuyPrice = totalBuyPrice / (newRow.Quantity - qToFind);
                            retVal = newRow.Price - unitBuyPrice;
                        }
                        if (qToFind > 0)
                        {
                            // If any of the transaction quantity could not be accounted for then simply send
                            // the 'normal' asset stack at this location to a negative quantity.
                            Assets.ChangeAssets(ownerID, newRow.StationID, newRow.ItemID,
                                 0, (int)AssetStatus.States.Normal, false, qToFind * -1, 0, false);
                        }
                    }
                    else
                    {
                        if (exDiag)
                        {
                            process.Append(Names.GetName(ownerID));
                            process.Append(" has no ");
                            process.Append(Items.GetItemName(newRow.ItemID));
                            process.Append("'s at ");
                            process.Append(Stations.GetStationName(newRow.StationID));
                            process.Append(". Consquently, no profit value can be calculated right now. This transaction has been flagged to have it's profit calculated when the next assets update occurs.");
                        }
                        // If there are no assets at the station where the sell took place then flag
                        // the transaction to have it's profit calculated when the next assets
                        // update is performed.
                        // Also, set the quantity of assets at this location negative.
                        newRow.CalcProfitFromAssets = true;

                        Assets.ChangeAssets(ownerID, newRow.StationID, newRow.ItemID, 0,
                            (int)AssetStatus.States.Normal, false, newRow.Quantity * -1, 0, false);
                    }
                }
            }
            catch (Exception ex)
            {
                new EMMADataException(ExceptionSeverity.Error,
                    "Problem calculating profit for new sell transaction.", ex);
            }

            return retVal;
        }
        public static EMMADataSet.AssetsDataTable GetAutoConAssets(long ownerID, long stationID,
            bool excludeContainers)
        {
            EMMADataSet.AssetsDataTable retVal = new EMMADataSet.AssetsDataTable();

            List<int> itemIDs = new List<int>();
            if (itemIDs == null || itemIDs.Count == 0) { itemIDs = new List<int>(); itemIDs.Add(0); }
            if (UserAccount.CurrentGroup.Settings.AutoCon_TradedItems)
            {
                itemIDs = UserAccount.CurrentGroup.TradedItems.GetAllItemIDs();
            }
            StringBuilder itemsString = new StringBuilder("");
            foreach (int itemID in itemIDs)
            {
                if (itemsString.Length > 0) { itemsString.Append(","); }
                itemsString.Append(itemID);
            }

            lock (assetsTableAdapter)
            {
                assetsTableAdapter.FillByAutoCon(retVal, ownerID, stationID,
                    itemsString.ToString(), excludeContainers);
            }
            return retVal;
        }
        public static EMMADataSet.AssetsDataTable GetAutoConAssets(long ownerID, string locationName,
            bool excludeContainers)
        {
            GroupLocation location = GroupLocations.GetLocationDetail(locationName);
            List<long> regionsIDs = location.Regions;
            List<long> stationIDs = location.Stations;
            if (regionsIDs == null || regionsIDs.Count == 0) { regionsIDs = new List<long>(); regionsIDs.Add(0); }
            if (stationIDs == null || stationIDs.Count == 0) { stationIDs = new List<long>(); stationIDs.Add(0); }

            StringBuilder regionsString = new StringBuilder("");
            StringBuilder stationsString = new StringBuilder("");
            foreach (int regionID in regionsIDs)
            {
                if (regionsString.Length > 0) { regionsString.Append(","); }
                regionsString.Append(regionID);
            }
            foreach (int stationID in stationIDs)
            {
                if (stationsString.Length > 0) { stationsString.Append(","); }
                stationsString.Append(stationID);
            }

            List<int> itemIDs = new List<int>();
            if (itemIDs == null || itemIDs.Count == 0) { itemIDs = new List<int>(); itemIDs.Add(0); }
            if (UserAccount.CurrentGroup.Settings.AutoCon_TradedItems)
            {
                itemIDs = UserAccount.CurrentGroup.TradedItems.GetAllItemIDs();
            }
            StringBuilder itemsString = new StringBuilder("");
            foreach (int itemID in itemIDs)
            {
                if (itemsString.Length > 0) { itemsString.Append(","); }
                itemsString.Append(itemID);
            }

            EMMADataSet.AssetsDataTable retVal = new EMMADataSet.AssetsDataTable();
            lock (assetsTableAdapter)
            {
                assetsTableAdapter.FillByAutoConAny(retVal, ownerID, stationsString.ToString(),
                    regionsString.ToString(), itemsString.ToString(), excludeContainers);
            }
            return retVal;
        }
 /// <summary>
 /// Return the assets owned by the specified characters and corps
 /// </summary>
 /// <param name="accessParams"></param>
 /// <returns></returns>
 public static EMMADataSet.AssetsDataTable GetAssets(List<AssetAccessParams> accessParams)
 {
     EMMADataSet.AssetsDataTable retVal = new EMMADataSet.AssetsDataTable();
     lock (assetsTableAdapter)
     {
         assetsTableAdapter.FillByAny(retVal, AssetAccessParams.BuildAccessList(accessParams), "", 0, 0, 0, false, true, 0);
     }
     return retVal;
 }
        /// <summary>
        /// This is called after an import of XML asset data but before the changes are commited
        /// to the database.
        /// The aim is to try to work out which assets have simply moved location and which have 
        /// been added or lost. Additionally, if items have moved, update the cost of the item 
        /// stack appropriately.
        /// </summary>
        /// <param name="assetData">The datatable containing the changes that will be applied to the assets database</param>
        /// <param name="charID"></param>
        /// <param name="corp"></param>
        /// <param name="changes">Contains changes in quantities of item stacks that were in the XML.</param>
        /// <param name="gained">A list of items that have been gained from 'nowhere'</param>
        /// <param name="lost">A list of items that have been totally lost from the owner's asset data.</param>
        public static void AnalyseChanges(EMMADataSet.AssetsDataTable assetData, long ownerID,
            AssetList changes, out AssetList gained, out AssetList lost)
        {
            // Note that 'changes' will only include items that exist in the XML from the API. i.e. Any
            // stacks that have been completely removed from a location will not show up.
            // To get these removed assets, we need to retrieve any 'unprocessed' assets from the
            // assetData table.
            gained = new AssetList();
            lost = new AssetList();

            // Add any unprocessed asset stacks to the 'changes' collection.
            // Although the main data changes have not yet been supplied to the database,
            // the processed flags have been set for the relevant asset rows.
            // This means that we can get the list of unprocessed assets direct from
            // the database.
            List<AssetAccessParams> accessParams = new List<AssetAccessParams>();
            accessParams.Add(new AssetAccessParams(ownerID));
            EMMADataSet.AssetsDataTable unprocMatches = new EMMADataSet.AssetsDataTable();
            Assets.GetAssets(unprocMatches, accessParams, 0, 0, 0, 0, false);

            foreach (EMMADataSet.AssetsRow unprocMatch in unprocMatches)
            {
                Asset change = new Asset(unprocMatch);
                // Ignore any assets 'for sale via contract' or 'in transit'
                if (change.Quantity != 0 && change.StatusID != (int)AssetStatus.States.ForSaleViaContract &&
                    change.StatusID != (int)AssetStatus.States.InTransit)
                {
                    change.Quantity *= -1; // These assets are effectively missing so invert the quantity.
                    changes.Add(change);
                }
            }

            // Note 'changes2' is a list of the same items as 'changes'.
            // It is needed because the filter functionallity changes the list which we cannot
            // do within the main foreach loop.
            AssetList changes2 = new AssetList();
            foreach (Asset change in changes)
            {
                changes2.Add(change);
            }

            // See if any of our 'missing' assets match transactions that are marked to have thier profit
            // calculated during an assets update.
            #region Update transactions with CalcProfitFromAssets flag
            EMMADataSet.TransactionsDataTable transData = new EMMADataSet.TransactionsDataTable();
            Transactions.AddTransByCalcProfitFromAssets(transData,
                UserAccount.CurrentGroup.GetFinanceAccessParams(APIDataType.Transactions), 0, true);

            foreach (EMMADataSet.TransactionsRow trans in transData)
            {
                long quantityRemaining = trans.Quantity;
                decimal totalBuyPrice = 0;

                changes.ItemFilter = "ItemID = " + trans.ItemID + " AND Quantity < 0";
                foreach (Asset change in changes.FiltredItems)
                {
                    // If we get a match then use the missing items's cost to calculate the
                    // transaction's profit.
                    // Note that we don't need to adjust any item quantities since either the
                    // changes have already been made or the item stack is 'unprocessed' and will
                    // be cleared out anyway.
                    long deltaQ = Math.Min(Math.Abs(change.Quantity), quantityRemaining);
                    // Adjust the quantity of the 'missing' items.
                    change.Quantity += deltaQ;
                    quantityRemaining -= deltaQ;
                    // Because this transaction's asset quantity change will have been made
                    // back when the transaction was processed, we will have some unexpected
                    // added items as well.
                    //
                    // E.g. Consider station A and B. At A, there are 2 units of item X, at B
                    // there is nothing.
                    // At a later date, the player moves 2 of X from A to B and sells it.
                    // When the sell transaction is processed, station B will be set to -2
                    // units of X and the transaction will be set as 'CalcProfitFromAssets'.
                    // When the asset update occurs, it will show zero for both locations so
                    // they will be unprocessed.
                    // This will result in 2 unexplained units gained at station B and 2 lost
                    // at station A. (We've gone from A=2,B=-2 to A=0 B=0)
                    changes2.ItemFilter = "ItemID = " + trans.ItemID + " AND LocationID = " + trans.StationID +
                        " AND Status = 1";
                    if (changes2.FiltredItems.Count > 0)
                    {
                        // We've already accounted for the cost of items, etc so just reduce the
                        // quantity.
                        changes2[0].Quantity -= deltaQ;
                    }

                    totalBuyPrice += (change.UnitBuyPrice * deltaQ);

                    if (quantityRemaining != trans.Quantity)
                    {
                        // We've found enough missing items to match this transaction either completely
                        // or partially so calculate it's profit and set it as completed.
                        trans.CalcProfitFromAssets = false;
                        trans.SellerUnitProfit = trans.Price - (totalBuyPrice / trans.Quantity);
                    }
                }
            }
            /*
            foreach (Asset change in changes)
            {
                // Note: because we are changing quantity within the foreach loop
                // we don't use the filter on the AssetList.
                // Instead, just use this if condition each time around.
                if (change.Quantity < 0 && !noTransItemIDs.Contains(change.ItemID))
                {
                    // If we get a match then use the missing items's cost to calculate the
                    // transaction's profit.
                    // Note that we don't need to adjust any item quantities since either the
                    // changes have already been made or the item stack is 'unprocessed' and will
                    // be cleared out anyway.
                    Transactions.AddTransByCalcProfitFromAssets(transData,
                        UserAccount.CurrentGroup.GetFinanceAccessParams(APIDataType.Transactions),
                        change.ItemID, true);
                    if (transData.Count == 0) { noTransItemIDs.Add(change.ItemID); }
                    foreach (EMMADataSet.TransactionsRow trans in transData)
                    {
                        if (trans.ItemID == change.ItemID && !completedTrans.Contains(trans.ID))
                        {
                            long quantityRemaining = trans.Quantity;
                            TransProcessData data = new TransProcessData();
                            if (processData.ContainsKey(trans.ID))
                            {
                                data = processData[trans.ID];
                                quantityRemaining = data.QuantityRemaining;
                            }
                            else
                            {
                                data.QuantityMatched = 0;
                                data.QuantityRemaining = trans.Quantity;
                                data.TotalBuyPrice = 0;
                                processData.Add(trans.ID, data);
                            }

                            long deltaQ = Math.Min(Math.Abs(change.Quantity), quantityRemaining);
                            // Adjust the quantity of the 'missing' items.
                            change.Quantity += deltaQ;
                            // Because this transaction's asset quantity change will have been made
                            // back when the transaction was processed, we will have some unexpected
                            // added items as well.
                            //
                            // E.g. Consider station A and B. At A, there are 2 units of item X, at B
                            // there is nothing.
                            // At a later date, the player moves 2 of X from A to B and sells it.
                            // When the sell transaction is processed, station B will be set to -2
                            // units of X and the transaction will be set as 'CalcProfitFromAssets'.
                            // When the asset update occurs, it will show zero for both locations so
                            // they will be unprocessed.
                            // This will result in 2 unexplained units gained at station B and 2 lost
                            // at station A. (We've gone from A=2,B=-2 to A=0 B=0)
                            changes2.ItemFilter = "ItemID = " + trans.ItemID + " AND LocationID = " + trans.StationID;
                            foreach (Asset change2 in changes2)
                            {
                                if (change2.Quantity > 0)
                                {
                                    // We've already accounted for the cost of items, etc so just reduce the
                                    // quantity.
                                    change2.Quantity -= deltaQ;
                                }
                            }

                            data.QuantityRemaining = data.QuantityRemaining - deltaQ;
                            data.QuantityMatched = data.QuantityMatched + deltaQ;
                            data.TotalBuyPrice = data.TotalBuyPrice + (change.UnitBuyPrice * deltaQ);

                            if (data.QuantityRemaining == 0)
                            {
                                // We've found enough missing items to match this transaction completely
                                // so calculate it's profit and set it as completed.
                                trans.CalcProfitFromAssets = false;
                                trans.SellerUnitProfit = trans.Price - (data.TotalBuyPrice / data.QuantityMatched);
                                completedTrans.Add(trans.ID);
                                if (uncompletedTrans.Contains(trans.ID)) { uncompletedTrans.Remove(trans.ID); }
                            }
                            else
                            {
                                // We havn't found enough missing items to completely match this transaction
                                // yet so add to to the list of uncompleted transactions.
                                uncompletedTrans.Add(trans.ID);
                            }
                        }
                    }
                }
            }

            // Calculate profits as best we can for any 'uncompleted' transactions.
            // i.e. those that we did not have enough missing items to match completely.
            foreach (long transID in uncompletedTrans)
            {
                EMMADataSet.TransactionsRow trans = transData.FindByID(transID);
                if (trans != null && processData.ContainsKey(transID))
                {
                    TransProcessData data = processData[transID];
                    trans.CalcProfitFromAssets = false;
                    trans.SellerUnitProfit = trans.Price - (data.TotalBuyPrice / data.QuantityMatched);
                }
            }
             * */
            // Update transactions database
            Transactions.Store(transData);
            #endregion

            // Work through the list of changes.
            //
            // If items have been added then see if there is a matching quantity that has been removed
            // somwhere. If so, use the old asset stack cost to set the cost for the new asset stack.
            // If we can't find a match for any added items then add them to the 'gained' list.
            //
            // By definititon, any items that have been removed and match stacks added elsewhere will
            // be matched by the above process. Any removed items that are left over are added to the
            // 'lost' list of assets.
            changes.ItemFilter = "";

            // If we're in manufacturing mode then we don't want to try and match
            // lost assets with gained assets until after the user has selected
            // what has been built, etc.
            if (!UserAccount.Settings.ManufacturingMode)
            {
                #region Try and match gains in one place against losses in another.
                List<int> zeroLossItemIDs = new List<int>();
                foreach (Asset change in changes)
                {
                    if (change.Quantity > 0 && !zeroLossItemIDs.Contains(change.ItemID))
                    {
                        int itemID = change.ItemID;
                        long locationID = change.LocationID;

                        changes2.ItemFilter = "ItemID = " + itemID + " AND Quantity < 0";
                        if (changes2.FiltredItems.Count == 0)
                        {
                            if (!zeroLossItemIDs.Contains(change.ItemID)) { zeroLossItemIDs.Add(change.ItemID); }
                        }

                        foreach (Asset change2 in changes2.FiltredItems)
                        {
                            if (change.Quantity > 0 && change2.Quantity < 0)
                            {
                                // Get the asset data lines associated with the two changes in asset quantities
                                // that we have found.
                                //bool got1 = Assets.AssetExists(assetData, charID, corp, locationID, itemID,
                                //    change.StatusID, change.ContainerID != 0, change.ContainerID, change.IsContainer,
                                //    true, true, change.AutoConExclude, ref assetID1);
                                //bool got2 = Assets.AssetExists(assetData, charID, corp, change2.LocationID, itemID,
                                //    change2.StatusID, change2.ContainerID != 0, change2.ContainerID, change2.IsContainer,
                                //    true, true, change2.AutoConExclude, ref assetID2);
                                Assets.AddAssetToTable(assetData, change.ID);
                                Assets.AddAssetToTable(assetData, change2.ID);

                                EMMADataSet.AssetsRow row1 = assetData.FindByID(change.ID);
                                EMMADataSet.AssetsRow row2 = assetData.FindByID(change2.ID);
                                Asset a1 = new Asset(row1, null);
                                Asset a2 = new Asset(row2, null);
                                long thisAbsDeltaQ = Math.Min(Math.Abs(change.Quantity), Math.Abs(change2.Quantity));

                                // If the rows are processed then the actual movement of items has already happened
                                // so we don't need to adjust quantities.
                                // If they are not processed then we need to adjust the quantities now.
                                if (!row1.Processed) { row1.Quantity += thisAbsDeltaQ; }
                                if (!row2.Processed) { row2.Quantity -= thisAbsDeltaQ; }

                                // If the stack we're adding to now has zero items in it then we can just
                                // remove it. Otherwise we need to work out the value of the new stack.
                                if (row1.Quantity > 0)
                                {
                                    // Calculate the cost of items in the new stack based on the
                                    // cost of items from the old stack.
                                    decimal newCost = 0;
                                    newCost = ((a1.TotalBuyPrice) + (a2.UnitBuyPrice * thisAbsDeltaQ)) /
                                        (a1.Quantity + thisAbsDeltaQ);
                                    row1.Cost = newCost;
                                    row1.CostCalc = true;
                                }
                                else if (row1.Quantity == 0) { row1.Delete(); }
                                if (row2.Quantity == 0) { row2.Delete(); }

                                change.Quantity -= thisAbsDeltaQ;
                                change2.Quantity += thisAbsDeltaQ;
                            }
                        }

                    }
                }
                #endregion
            }

            // We have some items left over that could not be accounted for from losses/gains
            // elsewhere. Add these to the appropriate 'lost' or 'gained' list.
            #region Add remaining unexplained changes to the appropriate lost/gained list
            foreach (Asset change in changes)
            {
                if (change.Quantity != 0)
                {
                    Asset unexplained = new Asset();
                    unexplained.ID = change.ID;
                    unexplained.ItemID = change.ItemID;
                    unexplained.LocationID = change.LocationID;
                    unexplained.Quantity = change.Quantity;
                    unexplained.StatusID = change.StatusID;
                    unexplained.IsContainer = change.IsContainer;
                    unexplained.Container = change.Container;
                    unexplained.AutoConExclude = change.AutoConExclude;
                    unexplained.OwnerID = change.OwnerID;
                    if (change.UnitBuyPricePrecalculated)
                    {
                        unexplained.UnitBuyPrice = change.UnitBuyPrice;
                        unexplained.UnitBuyPricePrecalculated = true;
                    }
                    else
                    {
                        unexplained.UnitBuyPrice = 0;
                        unexplained.UnitBuyPricePrecalculated = false;
                    }
                    if (change.Quantity > 0)
                    {
                        // If the unexplained assets are for sale via contract or in transit then we
                        // would expect them not to show up if they are in the same state as before.
                        // This being the case, we do not need to add them to the list of unexplained items.
                        if (change.StatusID != (int)AssetStatus.States.ForSaleViaContract &&
                            change.StatusID != (int)AssetStatus.States.InTransit) { gained.Add(unexplained); }
                    }
                    else
                    {
                        // If the unexplained assets are for sale via contract or in transit then we
                        // would expect them not to show up if they are in the same state as before.
                        // This being the case, we do not need to add them to the list of unexplained items.
                        if (change.StatusID != (int)AssetStatus.States.ForSaleViaContract &&
                            change.StatusID != (int)AssetStatus.States.InTransit) { lost.Add(unexplained); }
                    }
                }
            }
            #endregion
        }
        private void ProcessAssetsAsMarked()
        {
            try
            {
                _errorCount = 0;
                int counter = 0;
                int total = _gainedAssets.Count + _lostAssets.Count;

                EMMADataSet.AssetsDataTable assetChanges = new EMMADataSet.AssetsDataTable();
                List<Asset> assetsToRemove = new List<Asset>();
                _gainedAssets.ItemFilter = "";
                foreach (Asset gainedAsset in _gainedAssets)
                {
                    long assetID = 0;
                    APICharacter character = UserAccount.CurrentGroup.GetCharacter(gainedAsset.OwnerID);
                    Assets.AssetExists(assetChanges, gainedAsset.OwnerID,
                        gainedAsset.LocationID, gainedAsset.ItemID, gainedAsset.StatusID,
                        gainedAsset.ContainerID != 0, gainedAsset.ContainerID, gainedAsset.IsContainer,
                        false, true, gainedAsset.AutoConExclude, false, gainedAsset.EveItemInstanceID, ref assetID);
                    EMMADataSet.AssetsRow assetRow = assetChanges.FindByID(assetID);

                    counter++;
                    UpdateStatus(counter, total, "Updating gained assets data", "", false);

                    if (assetRow != null)
                    {
                        switch (gainedAsset.ChangeTypeID)
                        {
                            case AssetChangeTypes.ChangeType.Found:
                                assetRow.Cost = 0;
                                assetRow.CostCalc = true;
                                AssetsProduced.Add(gainedAsset);
                                assetsToRemove.Add(gainedAsset);
                                break;
                            //case AssetChangeTypes.ChangeType.Made:
                            //    assetRow.Cost = gainedAsset.UnitBuyPricePrecalculated ? gainedAsset.UnitBuyPrice : 0;
                            //    assetRow.CostCalc = gainedAsset.UnitBuyPricePrecalculated;
                            //    AssetsProduced.Add(gainedAsset);
                            //    assetsToRemove.Add(gainedAsset);
                            //    break;
                            case AssetChangeTypes.ChangeType.WasNeverMissing:
                                assetRow.Cost = 0;
                                assetRow.CostCalc = false;
                                assetsToRemove.Add(gainedAsset);
                                break;
                            case AssetChangeTypes.ChangeType.BoughtViaContract:
                                assetRow.Cost = 0;
                                assetRow.CostCalc = false;
                                assetRow.BoughtViaContract = true;
                                assetsToRemove.Add(gainedAsset);
                                break;
                            case AssetChangeTypes.ChangeType.CancelledContract:
                                List<AssetAccessParams> access = new List<AssetAccessParams>();
                                //bool corporate = false;
                                access.Add(new AssetAccessParams(assetRow.OwnerID));
                                AssetList assets = Assets.LoadAssets(access, new List<long>(), assetRow.ItemID, 0, 0,
                                    false, (int)AssetStatus.States.ForSaleViaContract, true, true);
                                if (assets.Count > 0)
                                {
                                    List<long> matchedAssetsForSale = new List<long>();
                                    long qToFind = assetRow.Quantity;
                                    decimal totalCost = 0;
                                    long costq = 0;
                                    bool costPreCalc = false;
                                    foreach (Asset a in assets)
                                    {
                                        if (qToFind > 0)
                                        {
                                            long deltaQ = Math.Min(a.Quantity, qToFind);
                                            qToFind -= deltaQ;
                                            totalCost += a.UnitBuyPricePrecalculated ? a.TotalBuyPrice : 0;
                                            costq += a.UnitBuyPricePrecalculated ? deltaQ : 0;
                                            if (a.UnitBuyPricePrecalculated) { costPreCalc = true; }
                                            matchedAssetsForSale.Add(a.ID);
                                        }
                                    }

                                    assetRow.Cost = (costq > 0 ? totalCost / costq : 0);
                                    assetRow.CostCalc = costPreCalc;
                                    // We've set the cost of the asset, now to remove the old one(s).
                                    foreach (long matchedAssetID in matchedAssetsForSale)
                                    {
                                        EMMADataSet.AssetsRow assetData = Assets.GetAssetDetail(matchedAssetID);
                                        assetData.Delete();
                                        Assets.UpdateDatabase(assetData);
                                    }
                                }
                                else
                                {
                                    // Can't find old asset so don't know cost.
                                    assetRow.Cost = 0;
                                    assetRow.CostCalc = false;
                                }
                                break;
                            case AssetChangeTypes.ChangeType.Unknown:
                                // Can only be 'unknown' if we're in manufacturing mode.
                                // let the cross check sort it out...
                                break;
                            default:
                                throw new EMMAException(ExceptionSeverity.Error, "Unexpected gained asset change type: '" +
                                    gainedAsset.ChangeType + "' ", true);
                                break;
                        }
                    }
                    else
                    {
                        // This indicates that the asset row was removed due to being marked as unprocessed.
                        // i.e. the gain in items was from -x to 0.
                        // We can happily ignore this.
                        //new EMMAException(ExceptionSeverity.Warning, "Could not find gained asset to update\r\n" +
                        //    "\tOwner: " + gainedAsset.OwnerID + "\r\n\tCorpAsset: " + gainedAsset.CorpAsset +
                        //    "\r\n\tLocation: " + gainedAsset.LocationID + "\r\n\tItem : " + gainedAsset.ItemID +
                        //    "\r\n\tStatus: " + gainedAsset.StatusID + "\r\n\tContainerID: " +
                        //    gainedAsset.ContainerID + "\r\n\tIsContainer: " + gainedAsset.IsContainer.ToString() +
                        //    "\r\n\tAutoConExclude: " + gainedAsset.AutoConExclude.ToString() +
                        //    "\r\n\tEveItemInstanceID: " + gainedAsset.EveItemInstanceID, true);
                        //_errorCount++;
                    }
                }
                foreach (Asset a in assetsToRemove)
                {
                    _gainedAssets.Remove(a);
                }

                assetsToRemove = new List<Asset>();
                _lostAssets.ItemFilter = "";
                foreach (Asset lostAsset in _lostAssets)
                {
                    counter++;
                    UpdateStatus(counter, total, "Updating lost assets data", "", false);

                    switch (lostAsset.ChangeTypeID)
                    {
                        case AssetChangeTypes.ChangeType.ForSaleViaContract:
                            EMMADataSet.AssetsRow assetRow = assetChanges.NewAssetsRow();
                            assetRow.AutoConExclude = true;
                            assetRow.ContainerID = lostAsset.ContainerID;
                            assetRow.CorpAsset = lostAsset.CorpAsset;
                            assetRow.Cost = lostAsset.UnitBuyPricePrecalculated ? lostAsset.UnitBuyPrice : 0;
                            assetRow.CostCalc = lostAsset.UnitBuyPricePrecalculated;
                            assetRow.IsContainer = lostAsset.IsContainer;
                            assetRow.ItemID = lostAsset.ItemID;
                            assetRow.EveItemID = 0;
                            assetRow.LocationID = lostAsset.LocationID;
                            assetRow.OwnerID = lostAsset.OwnerID;
                            assetRow.Processed = false;
                            assetRow.Quantity = lostAsset.Quantity * -1;
                            assetRow.RegionID = lostAsset.RegionID;
                            assetRow.ReprocExclude = true;
                            assetRow.Status = (int)AssetStatus.States.ForSaleViaContract;
                            assetRow.SystemID = lostAsset.SystemID;
                            assetRow.BoughtViaContract = false;
                            assetChanges.AddAssetsRow(assetRow);
                            assetsToRemove.Add(lostAsset);
                            break;
                        case AssetChangeTypes.ChangeType.DestroyedOrUsed:
                            AssetsLost.Add(lostAsset);
                            assetsToRemove.Add(lostAsset);
                            break;
                        case AssetChangeTypes.ChangeType.NotLost:
                            EMMADataSet.AssetsRow assetRow2 = assetChanges.NewAssetsRow();
                            assetRow2.AutoConExclude = lostAsset.AutoConExclude;
                            assetRow2.ContainerID = lostAsset.ContainerID;
                            assetRow2.CorpAsset = lostAsset.CorpAsset;
                            assetRow2.Cost = lostAsset.UnitBuyPricePrecalculated ? lostAsset.UnitBuyPrice : 0;
                            assetRow2.CostCalc = lostAsset.UnitBuyPricePrecalculated;
                            assetRow2.IsContainer = lostAsset.IsContainer;
                            assetRow2.ItemID = lostAsset.ItemID;
                            assetRow2.EveItemID = lostAsset.EveItemInstanceID;
                            assetRow2.LocationID = lostAsset.LocationID;
                            assetRow2.OwnerID = lostAsset.OwnerID;
                            assetRow2.Processed = false;
                            assetRow2.Quantity = lostAsset.Quantity * -1;
                            assetRow2.RegionID = lostAsset.RegionID;
                            assetRow2.ReprocExclude = lostAsset.ReprocessorExclude;
                            assetRow2.Status = (int)AssetStatus.States.Normal;
                            assetRow2.SystemID = lostAsset.SystemID;
                            assetRow2.BoughtViaContract = false;
                            assetChanges.AddAssetsRow(assetRow2);
                            assetsToRemove.Add(lostAsset);
                            break;
                        case AssetChangeTypes.ChangeType.Unknown:
                            // Can only be 'unknown' if we're in manufacturing mode.
                            // let the cross check sort it out...
                            break;
                        default:
                            throw new EMMAException(ExceptionSeverity.Error, "Unexpected lost asset change type: '" +
                                lostAsset.ChangeType + "' ", true);
                            break;
                    }
                }

                foreach (Asset a in assetsToRemove)
                {
                    _lostAssets.Remove(a);
                }
                if (assetChanges.Count > 0)
                {
                    // Put any items that have not actually been lost back into the database.
                    // e.g. an item that is 'for sale via contract' will already have been removed from the
                    // database by this point so we need to create a new record and put it back in.
                    Assets.UpdateDatabase(assetChanges);
                }

                if (UserAccount.Settings.ManufacturingMode)
                {
                    CrossCheckAssetChanges();
                }
            }
            catch (Exception ex)
            {
                EMMAException emmaEx = ex as EMMAException;
                if (emmaEx == null)
                {
                    new EMMAException(ExceptionSeverity.Error, "Problem processing asset changes " +
                        "unacknowledged assets view", ex);
                }
                MessageBox.Show("Problem processing asset changes in unacknowledged assets view:\r\n" +
                    ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                UpdateStatus(0, 0, "FinalTasks", "", true);
            }
        }
        /// <summary>
        /// Return the assets stored at the specified station
        /// </summary>
        /// <param name="stationID"></param>
        /// <returns></returns>
        /*static public EMMADataSet.AssetsDataTable GetAssetsAt(List<AccessParams> accessParams, int stationID)
        {
            EMMADataSet.AssetsDataTable retVal = new EMMADataSet.AssetsDataTable();
            assetsTableAdapter.FillByItemAndLocation(retVal, BuildAccessList(accessParams), "", stationID, 0);
            return retVal;
        }*/
        public static AssetList GetAssets(List<AssetAccessParams> accessParams, int itemID, List<long> stationIDs,
            List<long> regionIDs, bool includeInTransit, bool includeContainers, bool includeSingletons)
        {
            AssetList retVal = new AssetList();
            EMMADataSet.AssetsDataTable data = new EMMADataSet.AssetsDataTable();
            if (regionIDs == null || regionIDs.Count == 0) { regionIDs = new List<long>(); regionIDs.Add(0); }
            if (stationIDs == null || stationIDs.Count == 0) { stationIDs = new List<long>(); stationIDs.Add(0); }

            StringBuilder regionString = new StringBuilder("");
            StringBuilder stationString = new StringBuilder("");
            foreach (int regionID in regionIDs)
            {
                if (regionString.Length > 0) { regionString.Append(","); }
                regionString.Append(regionID);
            }
            foreach (int stationID in stationIDs)
            {
                if (stationString.Length > 0) { stationString.Append(","); }
                stationString.Append(stationID);
            }

            lock (assetsTableAdapter)
            {
                assetsTableAdapter.FillByItem(data, AssetAccessParams.BuildAccessList(accessParams),
                    regionString.ToString(), stationString.ToString(), itemID,
                    includeInTransit, includeContainers, includeSingletons);
            }

            foreach (EMMADataSet.AssetsRow asset in data)
            {
                retVal.Add(new Asset(asset));
            }

            return retVal;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="table"></param>
        /// <param name="ownerID"></param>
        /// <param name="corpAsset"></param>
        /// <param name="locationID"></param>
        /// <param name="itemID"></param>
        /// <param name="containerID"></param>
        /// <param name="isContainer"></param>
        /// <param name="assetID"></param>
        /// <returns></returns>
        public static bool AssetExists(EMMADataSet.AssetsDataTable table, long ownerID,
            long locationID, int itemID, int status, bool isContained, long containerID, bool isContainer,
            bool processed, bool ignoreProcessed, bool autoConExclude, bool ignoreAutoConEx,
            long eveItemInstanceID, ref long assetID)
        {
            bool? exists = false;
            long? assetRowID = 0;

            lock (assetsTableAdapter)
            {
                // Have to be carefull here cos the row could well already exist in our table...
                EMMADataSet.AssetsDataTable tmpTable = new EMMADataSet.AssetsDataTable();
                assetsTableAdapter.FillAssetExists(tmpTable, ownerID, locationID, itemID, status,
                    isContained, containerID, isContainer, processed, ignoreProcessed, autoConExclude,
                    ignoreAutoConEx, eveItemInstanceID, ref exists, ref assetRowID);
                long id = assetRowID.HasValue ? assetRowID.Value : 0;
                EMMADataSet.AssetsRow row = table.FindByID(id);
                if (row == null && id != 0)
                {
                    EMMADataSet.AssetsRow dbrow = tmpTable.FindByID(id);
                    table.ImportRow(dbrow);
                }
            }

            assetID = assetRowID.HasValue ? assetRowID.Value : 0;
            return exists.HasValue ? exists.Value : false;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="ownerID"></param>
        /// <param name="corpAsset"></param>
        /// <param name="locationID"></param>
        /// <param name="itemID"></param>
        /// <param name="containerID"></param>
        /// <param name="status"></param>
        /// <param name="autoConExclude"></param>
        /// <param name="deltaQuatnity"></param>
        public static void BuyAssets(long ownerID, long stationID, int itemID,
            long deltaQuantity, decimal addedItemsCost, DateTime assetsEffectiveDate,
            DateTime transactionDate)
        {
            long systemID = 0, regionID = 0;

            EveDataSet.staStationsRow station = Stations.GetStation(stationID);
            if (station != null)
            {
                systemID = station.solarSystemID;
                regionID = station.regionID;
            }

            // The situation described in the comments below should never arrise, just ignore it.

            //AssetAccessParams access = new AssetAccessParams(ownerID, !corpAsset, corpAsset);
            //List<AssetAccessParams> accessParams = new List<AssetAccessParams>();
            //accessParams.Add(access);
            //List<int> stationIDs= new List<int>();
            //stationIDs.Add(stationID);
            //List<int> regionIDs = new List<int>();
            //regionIDs.Add(regionID);

            //long currentQ = GetTotalQuantity(accessParams, itemID, stationIDs, regionIDs, false, false);

            // If the current quantity of assets at the station where the buy transaction occurs is less
            // that zero then we must have some transactions marked with the 'calc profit from assets'
            // flag.
            // We can use the value of these bought items to calculate the profit instead.
            // Note: Since this buy transaction must be occuring AFTER the sell transactions, these
            // cannot be the items that were sold. However, they must have gone somewhere and if the
            // quantity is just added to the negative amount already present then the purchase price
            // will effectively be 'lost'. This would be even worse than a little inaccuracy in profit
            // calculations.
            //if (currentQ < 0)
            //{

            //}
            //else
            //{
            //    lock (assetsTableAdapter)
            //    {
            //        assetsTableAdapter.AddQuantity(ownerID, corpAsset, itemID, stationID, systemID,
            //            regionID, (int)AssetStatus.States.Normal, 0, false, deltaQuantity, addedItemsCost, true);
            //    }
            //}

            if (assetsEffectiveDate.CompareTo(transactionDate) < 0)
            {
                // If the transaction occured after the last assets update effetive date then
                // just add the asset to the database.
                lock (assetsTableAdapter)
                {
                    assetsTableAdapter.AddQuantity(ownerID, itemID, stationID, systemID,
                        regionID, (int)AssetStatus.States.Normal, 0, false, deltaQuantity, addedItemsCost, true);
                }
            }
            else
            {
                // NOTE: Ideally, this shouldn't happen but it's here just in case.

                // If the transaction occured before the last assets update effetive date then
                // it must already be in the database.
                // Try and find it to set it's cost.
                long qToFind = deltaQuantity;
                bool updates = false;
                EMMADataSet.AssetsDataTable assetData = new EMMADataSet.AssetsDataTable();
                List<AssetAccessParams> accessParams = new List<AssetAccessParams>();
                accessParams.Add(new AssetAccessParams(ownerID));
                AssetList assets = Assets.LoadAssets(accessParams, new List<long>(), itemID,
                    0, 0, false, (int)AssetStatus.States.Normal, true, false);
                foreach (Asset asset in assets)
                {
                    if (qToFind > 0 && (asset.UnitBuyPrice == 0 || !asset.UnitBuyPricePrecalculated))
                    {
                        long deltaQ = Math.Min(asset.Quantity, qToFind);
                        Assets.AddAssetToTable(assetData, asset.ID);
                        EMMADataSet.AssetsRow assetRow = assetData.FindByID(asset.ID);
                        assetRow.Cost = addedItemsCost;
                        assetRow.CostCalc = true;
                        updates = true;
                        qToFind -= deltaQ;
                    }
                }
                if (updates) { Assets.UpdateDatabase(assetData); }
            }
        }
        /// <summary>
        /// Return a list of assets that meet the specified parameters.
        /// Note that the list does NOT include assets stored within other containers, ships, etc.
        /// </summary>
        /// <param name="accessParams"></param>
        /// <param name="regionIDs"></param>
        /// <param name="itemID"></param>
        /// <param name="locationID"></param>
        /// <returns></returns>
        public static AssetList LoadAssetsByItemAndContainersOfItem(List<AssetAccessParams> accessParams,
            List<long> regionIDs, int itemID, long locationID, long systemID, bool containersOnly,
            int status, bool excludeContainers)
        {
            AssetList retVal = new AssetList();
            EMMADataSet.AssetsDataTable table = new EMMADataSet.AssetsDataTable();
            string regionString = "";
            // Do not do this, just pass in an empty list and it'll match all regions.
            //if (regionIDs.Count == 0) { regionIDs.Add(0); }
            foreach (int region in regionIDs)
            {
                regionString = regionString + (regionString.Length == 0 ? "" : ",") + region;
            }
            lock (assetsTableAdapter)
            {
                assetsTableAdapter.FillByItemAndContainersOfItem(table,
                    AssetAccessParams.BuildAccessList(accessParams), regionString, systemID,
                    locationID, itemID, containersOnly, false, status);
            }

            foreach (EMMADataSet.AssetsRow row in table)
            {
                if (row.Quantity > 0 && (!row.IsContainer || !excludeContainers))
                {
                    Asset asset = new Asset(row, null);
                    retVal.Add(asset);
                }
            }
            return retVal;
        }
        /// <summary>
        /// Return a list of assets that meet the specified parameters.
        /// </summary>
        public static AssetList LoadReprocessableAssets(long ownerID, long stationID,
            int status, bool includeContainers, bool includeNonContainers)
        {
            AssetList retVal = new AssetList();
            EMMADataSet.AssetsDataTable table = new EMMADataSet.AssetsDataTable();

            lock (assetsTableAdapter)
            {
                assetsTableAdapter.FillByReprocessables(table, ownerID, stationID,
                    status, includeContainers, includeNonContainers);
            }

            foreach (EMMADataSet.AssetsRow row in table)
            {
                if (row.Quantity > 0)
                {
                    Asset asset = new Asset(row, null);
                    retVal.Add(asset);
                }
            }
            return retVal;
        }
        /// <summary>
        /// Retrieve assets that are stored in the specified container 
        /// </summary>
        /// <param name="container"></param>
        /// <returns></returns>
        public static AssetList LoadAssets(Asset container, int itemID)
        {
            AssetList retVal = new AssetList();
            EMMADataSet.AssetsDataTable table = new EMMADataSet.AssetsDataTable();
            lock (assetsTableAdapter)
            {
                assetsTableAdapter.FillByContainerID(table, container.ID, itemID);
            }

            foreach (EMMADataSet.AssetsRow row in table)
            {
                Asset asset = new Asset(row, container);
                retVal.Add(asset);
            }
            return retVal;
        }
        public static bool GetResultsPage(int startPos, int pageSize, ref AssetList assets)
        {
            if (startPos <= 0) startPos = 1;
            EMMADataSet.AssetsDataTable table = new EMMADataSet.AssetsDataTable();
            lock (assetsTableAdapter)
            {
                assetsTableAdapter.FillByResultsPage(table, startPos, pageSize);
            }
            foreach (EMMADataSet.AssetsRow asset in table)
            {
                assets.Add(new Asset(asset, null));
            }

            return table.Count == pageSize;
        }
            public static decimal GetBuyPrice(long ownerID, int itemID, long stationID,
            long quantity, long recentPurchasesToIgnore)
            {
            decimal retVal = 0, blank1 = 0;

            // First try and get an asset stack at this location and use it's cost.
            EMMADataSet.AssetsDataTable assets = new EMMADataSet.AssetsDataTable();
            List<AssetAccessParams> accessParams = new List<AssetAccessParams>();
            //bool corp = false;
            //APICharacter charObj = UserAccount.CurrentGroup.GetCharacter(ownerID, ref corp);
            accessParams.Add(new AssetAccessParams(ownerID));
            Assets.GetAssets(assets, accessParams, stationID, 0, itemID);
            foreach (EMMADataSet.AssetsRow asset in assets)
            {
                if (retVal == 0 ||
                    asset.Status == (int)AssetStatus.States.Normal)
                {
                    retVal = asset.CostCalc ? 0 : asset.Cost;
                }
            }

            if (retVal == 0)
            {
                // If we can't find an asset stack then fall back to the old method of
                // working out approximate cost from transactions.
                List<FinanceAccessParams> financeAccessParams = new List<FinanceAccessParams>();
                financeAccessParams.Add(new FinanceAccessParams(ownerID));
                List<int> itemIDs = new List<int>();
                itemIDs.Add(itemID);
                List<long> stationIDs = new List<long>();
                stationIDs.Add(stationID);

                if (!UserAccount.CurrentGroup.ItemValues.UseReprocessValGet(itemID) &&
                    !UserAccount.CurrentGroup.ItemValues.ForceDefaultBuyPriceGet(itemID))
                {
                    Transactions.GetAverageBuyPrice(financeAccessParams, itemIDs, stationIDs, new List<long>(), quantity,
                        recentPurchasesToIgnore, ref retVal, ref blank1, true);
                }

                if (retVal == 0 || UserAccount.CurrentGroup.ItemValues.ForceDefaultBuyPriceGet(itemID))
                {
                    retVal = UserAccount.CurrentGroup.ItemValues.GetBuyPrice(itemID,
                        Stations.GetStation(stationID).regionID);
                }
            }

            return retVal;
        }
        private void UpdateAssetsFromXML(CharOrCorp corc, XmlDocument xml)
        {
            DateTime earliestUpdate = GetLastAPIUpdateTime(corc, APIDataType.Assets).AddHours(23);
            EMMADataSet.AssetsDataTable assetData = new EMMADataSet.AssetsDataTable();
            DateTime dataDate = DateTime.MinValue;

            try
            {
                XmlNodeList assetList = null;

                UpdateStatus(0, 1, "Getting asset data from file", "", false);

                dataDate = EveAPI.GetDataTime(xml);
                DateTime assetsEffectiveDate = corc == CharOrCorp.Char ?
                    Settings.CharAssetsEffectiveDate : Settings.CorpAssetsEffectiveDate;
                if (dataDate.CompareTo(assetsEffectiveDate) < 0)
                {
                    UpdateStatus(1, 1, "Error", "This data in this file is from " + dataDate.ToString() +
                        ". EMMA has already imported asset data dated " + assetsEffectiveDate + " therefore the" +
                        " database will not be updated.", true);
                    assetList = null;
                }
                else
                {
                    assetList = EveAPI.GetResults(xml);
                    UpdateStatus(1, 1, "", assetList.Count + " asset data lines found.", false);
                }

                if (assetList != null)
                {
                    // Set the 'processed' flag to false for all of this char/corp's assets.
                    Assets.SetProcessedFlag(corc == CharOrCorp.Corp ? _corpID : _charID, (int)AssetStatus.States.Normal, false);
                    Assets.SetProcessedFlag(corc == CharOrCorp.Corp ? _corpID : _charID, (int)AssetStatus.States.ForSaleViaMarket, false);
                    Assets.SetProcessedFlag(corc == CharOrCorp.Corp ? _corpID : _charID, (int)AssetStatus.States.ForSaleViaContract, false);
                    Assets.SetProcessedFlag(corc == CharOrCorp.Corp ? _corpID : _charID, (int)AssetStatus.States.InTransit, false);

                    AssetList changes = new AssetList();

                    // Create an in-memory datatable with all of the changes required to the assets
                    // database in order to reflect the data in the xml file.
                    UpdateAssets(assetData, assetList, 0, corc, 0, changes);
                    // Use the currently active sell order to account for assets that appear to be
                    // missing.
                    UpdateStatus(0, 0, "Processing active sell orders", "", false);
                    Assets.ProcessSellOrders(assetData, changes, corc == CharOrCorp.Corp ? _corpID : _charID);
                    UpdateStatus(0, 0, "", "Complete", false);
                    // Use transactions that occured after the effective date of the asset data file
                    // to ensure that the asset list is as up-to-date as possible.
                    UpdateStatus(0, 0, "Updating assets from transactions that occur after " +
                        "the asset file's effective date", "", false);
                    long maxID = Assets.UpdateFromTransactions(assetData, changes, _charID, _corpID,
                        corc == CharOrCorp.Corp, dataDate);
                    if (corc == CharOrCorp.Char) { Settings.CharAssetsTransUpdateID = maxID; }
                    else { Settings.CorpAssetsTransUpdateID = maxID; }
                    UpdateStatus(0, 0, "", "Complete", false);

                    AssetList gained = new AssetList();
                    AssetList lost = new AssetList();
                    if ((corc == CharOrCorp.Char && Settings.FirstUpdateDoneAssetsChar) ||
                        (corc == CharOrCorp.Corp && Settings.FirstUpdateDoneAssetsCorp))
                    {
                        UpdateStatus(0, 0, "Analysing changes to assets", "", false);
                        Assets.AnalyseChanges(assetData, corc == CharOrCorp.Corp ? _corpID : _charID,
                            changes, out gained, out lost);
                        UpdateStatus(0, 0, "", "Complete", false);
                    }
                    // If this is the first assets update then we want to try and assign sensible cost
                    // values to assets that we have not yet got a value for.
                    if ((corc == CharOrCorp.Char && !Settings.FirstUpdateDoneAssetsChar) ||
                        (corc == CharOrCorp.Corp && !Settings.FirstUpdateDoneAssetsCorp))
                    {
                        Assets.AssignApproxCosts(assetData, corc == CharOrCorp.Corp ? _corpID : _charID);
                    }

                    if (corc == CharOrCorp.Char)
                    {
                        _unacknowledgedGains = gained;
                        _unacknowledgedLosses = lost;
                    }
                    else
                    {
                        _corpUnacknowledgedGains = gained;
                        _corpUnacknowledgedLosses = lost;
                    }

                    UpdateStatus(0, 0, "Updating assets database", "", false);
                    Assets.UpdateDatabase(assetData);
                    UpdateStatus(0, 0, "", "Complete", false);

                    // Set all 'for sale via contract' and 'in transit' assets in the database to processed.
                    // These types of assets would not be expected to show up in either the XML from the
                    // API or the list of current market orders.
                    // Any assets of these types that have been moved to a different state (e.g. in transit
                    // items that have arrived or contracts that have expired) will have been updated already
                    // in this method or ProcessSellOrders.
                    // Therefore, the ones that are left are still in the same situation as before.
                    // i.e. either 'for sale via contract' or 'in transit'.
                    // We set them to processed to prevent them from being removed along with other
                    // unprocessed assets.
                    Assets.SetProcessedFlag(corc == CharOrCorp.Corp ? _corpID : _charID,
                        (int)AssetStatus.States.ForSaleViaContract, true);
                    Assets.SetProcessedFlag(corc == CharOrCorp.Corp ? _corpID : _charID,
                        (int)AssetStatus.States.InTransit, true);
                    // Clear any remaining assets that have not been processed.
                    Assets.ClearUnProcessed(corc == CharOrCorp.Corp ? _corpID : _charID, false);
                    Assets.SetProcessedFlag(corc == CharOrCorp.Corp ? _corpID : _charID, 0, false);

                    UpdateStatus(0, 0, assetData.Count + " asset database entries modified.", "", false);

                    // Update the assets effective date setting.
                    // Also set the 'FirstUpdateDone' flag
                    if (corc == CharOrCorp.Char)
                    {
                        Settings.CharAssetsEffectiveDate = dataDate;
                        Settings.FirstUpdateDoneAssetsChar = true;
                    }
                    else
                    {
                        Settings.CorpAssetsEffectiveDate = dataDate;
                        if (!Settings.FirstUpdateDoneAssetsCorp)
                        {
                            Settings.FirstUpdateDoneAssetsCorp = true;
                            foreach (EVEAccount account in UserAccount.CurrentGroup.Accounts)
                            {
                                foreach (APICharacter character in account.Chars)
                                {
                                    if (character.CharID != _charID && character.CorpID == _corpID)
                                    {
                                        Settings.FirstUpdateDoneAssetsCorp = true;
                                    }
                                }
                            }
                        }
                    }

                    UpdateStatus(1, 1, "", "Complete", true);
                }
            }
            catch (Exception ex)
            {
                EMMAException emmaEx = ex as EMMAException;
                if (emmaEx == null)
                {
                    // If we've caught a standard exception rather than an EMMA one then log it be creating a
                    // new exception.
                    // Note that we don't need to actually throw it..
                    emmaEx = new EMMAException(ExceptionSeverity.Error, "Error when processing assets data", ex);
                }

                UpdateStatus(-1, -1, "Error", ex.Message, true);
                SetLastAPIUpdateError(corc, APIDataType.Assets, ex.Message);
            }

            if (UpdateEvent != null)
            {
                if (_unacknowledgedLosses == null) { _unacknowledgedLosses = new AssetList(); }
                if (_unacknowledgedGains == null) { _unacknowledgedGains = new AssetList(); }
                if (_corpUnacknowledgedLosses == null) { _corpUnacknowledgedLosses = new AssetList(); }
                if (_corpUnacknowledgedGains == null) { _corpUnacknowledgedGains = new AssetList(); }

                if ((corc == CharOrCorp.Char && _unacknowledgedLosses.Count + _unacknowledgedGains.Count == 0) ||
                    (corc == CharOrCorp.Corp && _corpUnacknowledgedGains.Count + _corpUnacknowledgedLosses.Count == 0))
                {
                    UpdateEvent(this, new APIUpdateEventArgs(APIDataType.Assets,
                        corc == CharOrCorp.Char ? _charID : _corpID,
                        APIUpdateEventType.UpdateCompleted));
                }
                else
                {
                    SetLastAPIUpdateError(corc, APIDataType.Assets, "AWAITING ACKNOWLEDGEMENT");
                    UpdateEvent(this, new APIUpdateEventArgs(APIDataType.Assets,
                        corc == CharOrCorp.Char ? _charID : _corpID,
                        APIUpdateEventType.AssetsAwaitingAcknowledgement));
                }
            }
        }
        /// <summary>
        /// Get detailed information on the specified asset
        /// </summary>
        /// <param name="stationID"></param>
        /// <param name="itemID"></param>
        /// <returns></returns>
        public static EMMADataSet.AssetsRow GetAssetDetail(long ID)
        {
            EMMADataSet.AssetsRow retVal = null;
            EMMADataSet.AssetsDataTable table = new EMMADataSet.AssetsDataTable();
            lock (assetsTableAdapter)
            {
                assetsTableAdapter.FillByID(table, ID);
            }

            if (table != null)
            {
                if (table.Count > 0)
                {
                    retVal = table[0];
                }
            }
            return retVal;
        }
        public static void UpdateExchangeAssets(Contract contract)
        {
            bool buy = false;
            if (contract.Collateral < 0)
            {
                buy = true;
            }

            bool corporate = false;
            bool updateAssets = false;
            APICharacter character = UserAccount.CurrentGroup.GetCharacter(contract.OwnerID, ref corporate);
            DateTime assetsEffectiveDate = corporate ? character.Settings.CorpAssetsEffectiveDate :
                character.Settings.CharAssetsEffectiveDate;
            // We only need to worry about updating assets if the last assets update was
            // before the date of the contract (note 'IssueDate' = item exchange completed date)
            if (assetsEffectiveDate.CompareTo(contract.IssueDate) < 0) { updateAssets = true; }

            foreach (ContractItem item in contract.Items)
            {
                if (!buy)
                {
                    long qToFind = item.Quantity;
                    decimal itemBuyPrice = 0;

                    // Look for assets that are marked as 'for sale via contract'
                    AssetList assets = Assets.LoadAssets(
                        UserAccount.CurrentGroup.GetAssetAccessParams(APIDataType.Assets),
                        new List<long>(), item.ItemID, 0, 0, false,
                        (int)AssetStatus.States.ForSaleViaContract, true, true);

                    LookForAssetMatch(contract, updateAssets, ref qToFind, ref itemBuyPrice,
                        assets, false);
                    if (qToFind > 0)
                    {
                        LookForAssetMatch(contract, updateAssets, ref qToFind, ref itemBuyPrice,
                            assets, true);
                    }

                    if (qToFind > 0)
                    {
                        // Can't find any/enough assets 'for sale via contract' so check normal
                        // assets
                        assets = Assets.LoadAssets(
                            UserAccount.CurrentGroup.GetAssetAccessParams(APIDataType.Assets),
                            new List<long>(), item.ItemID, 0, 0, false,
                            (int)AssetStatus.States.Normal, true, false);

                        LookForAssetMatch(contract, updateAssets, ref qToFind, ref itemBuyPrice,
                            assets, false);
                        if (qToFind > 0)
                        {
                            LookForAssetMatch(contract, updateAssets, ref qToFind, ref itemBuyPrice,
                               assets, false);
                        }
                    }

                    long qFound = (item.Quantity - qToFind);
                    item.BuyPrice = (qFound > 0 ? (itemBuyPrice / qFound) : 0);
                }
                else
                {
                    // buy contract, first look for assets that are marked as 'bought via contract'
                    // If there are none then just add the asset directly.
                    long qToFind = item.Quantity;
                    bool updates = false;
                    EMMADataSet.AssetsDataTable assetData = new EMMADataSet.AssetsDataTable();
                    Assets.GetAssetsBoughtViaContract(assetData,
                        UserAccount.CurrentGroup.GetAssetAccessParams(APIDataType.Assets), item.ItemID);

                    foreach (EMMADataSet.AssetsRow asset in assetData)
                    {
                        if (qToFind > 0 && contract.OwnerID == asset.OwnerID)
                        {
                            long deltaQ = Math.Min(asset.Quantity, qToFind);
                            asset.Cost = -1 * item.SellPrice;
                            asset.CostCalc = true;
                            asset.BoughtViaContract = false;
                            updates = true;
                            // Note, we don't update asset quantities because the asset is
                            // clearly already there. (We found it by searching the database)
                            qToFind -= deltaQ;
                        }
                    }
                    if (qToFind > 0)
                    {
                        // We havn't found enough assets to match the contract.
                        // Check assets 'bought via contract' for all corps/chars in the report group
                        foreach (EMMADataSet.AssetsRow asset in assetData)
                        {
                            if (qToFind > 0)
                            {
                                long deltaQ = Math.Min(asset.Quantity, qToFind);
                                asset.Cost = -1 * item.SellPrice;
                                asset.CostCalc = true;
                                asset.BoughtViaContract = false;
                                updates = true;
                                qToFind -= deltaQ;
                            }
                        }
                    }
                    if (qToFind > 0)
                    {
                        // Still havn't managed to match everything. If we're updating assets
                        // (contract date later than asset effective date) then just create
                        // the new asset records directly.
                        // If we're not updating assets then look for an asset of the same type
                        // that does not have it's cost calculated and set that.
                        if (updateAssets)
                        {
                            Assets.ChangeAssets(contract.OwnerID, contract.PickupStationID,
                                item.ItemID, 0, (int)AssetStatus.States.Normal, false, qToFind,
                                -1 * item.SellPrice, true);
                        }
                        else
                        {
                            // Try to find existing assets that are the same type and do not have
                            // a calculated cost.
                            AssetList assets = Assets.LoadAssets(
                                UserAccount.CurrentGroup.GetAssetAccessParams(APIDataType.Assets),
                                new List<long>(), item.ItemID, 0, 0, false,
                                (int)AssetStatus.States.Normal, true, false);
                            foreach (Asset asset in assets)
                            {
                                if (qToFind > 0 && contract.OwnerID == asset.OwnerID &&
                                    (asset.UnitBuyPrice == 0 || !asset.UnitBuyPricePrecalculated))
                                {
                                    long deltaQ = Math.Min(asset.Quantity, qToFind);
                                    Assets.AddAssetToTable(assetData, asset.ID);
                                    EMMADataSet.AssetsRow assetRow = assetData.FindByID(asset.ID);
                                    assetRow.Cost = -1 * item.SellPrice;
                                    assetRow.CostCalc = true;
                                    updates = true;
                                    qToFind -= deltaQ;
                                }
                            }
                            if (qToFind > 0)
                            {
                                foreach (Asset asset in assets)
                                {
                                    if (qToFind > 0 &&
                                        (asset.UnitBuyPrice == 0 || !asset.UnitBuyPricePrecalculated))
                                    {
                                        long deltaQ = Math.Min(asset.Quantity, qToFind);
                                        Assets.AddAssetToTable(assetData, asset.ID);
                                        EMMADataSet.AssetsRow assetRow = assetData.FindByID(asset.ID);
                                        assetRow.Cost = -1 * item.SellPrice;
                                        assetRow.CostCalc = true;
                                        updates = true;
                                        qToFind -= deltaQ;
                                    }
                                }
                            }
                        }
                    }

                    if (updates) { Assets.UpdateDatabase(assetData); }

                }

            }
        }
        private void CrossCheckAssetChanges(bool showAndHideData)
        {
            // Only allow one of these checks to be running at once. If a later check
            // attempts to start then it must wait for the current one to finish and then run.
            lock (_lostAssets)
            {
                if (showAndHideData) { HideData(); }

                try
                {
                    EMMADataSet.AssetsDataTable assetChanges = new EMMADataSet.AssetsDataTable();

                    // Try and match one character/corp gains to anothers losses.
                    List<Asset> gAssetsToRemove = new List<Asset>();
                    for (int i = 0; i < _gainedAssets.Count; i++)
                    {
                        UpdateStatus(i, _gainedAssets.Count, "", "", false);

                        Asset gainedAsset = _gainedAssets[i];
                        decimal totalCost = 0;
                        long qRemaining = gainedAsset.Quantity;
                        _lostAssets.ItemFilter = "ItemID = " + gainedAsset.ItemID;
                        List<Asset> assetsToRemove = new List<Asset>();
                        for (int j = 0; j < _lostAssets.FiltredItems.Count; j++)
                        {
                            Asset lostAsset = (Asset)_lostAssets.FiltredItems[j];

                            // If a match is found then use the cost of the lost items to calculate
                            // the cost of the gained items.
                            if (lostAsset.Quantity < 0 && gainedAsset.Quantity > 0)
                            {
                                long deltaQ = Math.Min(qRemaining, Math.Abs(lostAsset.Quantity));
                                totalCost += lostAsset.UnitBuyPrice * deltaQ;
                                qRemaining -= deltaQ;

                                lostAsset.Quantity += deltaQ;
                            }
                            if (lostAsset.Quantity == 0) { assetsToRemove.Add(lostAsset); }
                        }
                        // Remove any lost items that have been accounted for.
                        foreach (Asset a in assetsToRemove)
                        {
                            _lostAssets.FiltredItems.Remove(a);
                        }

                        // If we found some lost items to match against this gained item then
                        // retrieve the gained asset row from the database and recalculate
                        // the cost of the stack.
                        if (qRemaining < gainedAsset.Quantity)
                        {
                            long assetID = 0;
                            APICharacter character = UserAccount.CurrentGroup.GetCharacter(gainedAsset.OwnerID);
                            Assets.AssetExists(assetChanges, gainedAsset.OwnerID,
                                gainedAsset.LocationID, gainedAsset.ItemID, gainedAsset.StatusID,
                                gainedAsset.ContainerID != 0, gainedAsset.ContainerID, gainedAsset.IsContainer,
                                false, true, gainedAsset.AutoConExclude, false, gainedAsset.EveItemInstanceID,
                                ref assetID);
                            EMMADataSet.AssetsRow assetRow = assetChanges.FindByID(assetID);

                            // Note if the asset row is null then it is not in the database.
                            // i.e. we previously had a negative quantity of items here.
                            // In this case, we can try and match the assets with a transaction
                            // market with the 'calcProfitFromAssets' flag.
                            if (assetRow != null)
                            {
                                long totalQ = gainedAsset.Quantity - qRemaining;
                                if (assetRow.Quantity > gainedAsset.Quantity && assetRow.CostCalc)
                                {
                                    // If the asset stack contains more items than our 'gained' record
                                    // and it alreay has a calculated cost then take the stack's current
                                    // cost into account as well.
                                    totalCost += assetRow.Cost * (assetRow.Quantity - gainedAsset.Quantity);
                                    totalQ += assetRow.Quantity - gainedAsset.Quantity;
                                }
                                assetRow.Cost = totalCost / totalQ;
                                assetRow.CostCalc = true;
                            }
                            else
                            {
                            }
                        }

                        gainedAsset.Quantity = qRemaining;
                        if (gainedAsset.Quantity == 0)
                        {
                            gAssetsToRemove.Add(gainedAsset);
                        }
                    }

                    // Remove any gained items that have been accounted for.
                    foreach (Asset a in gAssetsToRemove)
                    {
                        _gainedAssets.Remove(a);
                    }

                    Assets.UpdateDatabase(assetChanges);

                    _lostAssets.ItemFilter = "";
                    _gainedAssets.ItemFilter = "";
                }
                catch (Exception ex)
                {
                    EMMAException emmaEx = ex as EMMAException;
                    if (emmaEx == null)
                    {
                        new EMMAException(ExceptionSeverity.Error, "Problem cross checking asset changes in " +
                            "unacknowledged assets view", ex);
                    }
                    MessageBox.Show("Problem cross checking asset changes in unacknowledged assets view:\r\n" +
                        ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }

                if (showAndHideData) { ShowData(); }
            }
        }