
using CsvCount_ES.ResultModel;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CsvCount_ES
{
    public partial class Form1 : Form
    {
        //key:newsid字段，也是itemid字段,value:int-次数累计
        private ConcurrentDictionary<string, int> totalCountDic = new ConcurrentDictionary<string, int>();

        //链式结构 key: Int64 累加(对应分块的块号)， value：这一块的ListModel的第一行和最后一行的string内容
        private ConcurrentDictionary<Int64, ListModel> listModelDic = new ConcurrentDictionary<Int64, ListModel>();

        private static Int64 sn = 0;
        //警告 在 32 位 Intel 计算机上分配 64 位值不是原子操作；即该操作不是线程安全的。这意味着，如果两个人同时将一个值分配给一个静态 Int64 字段，则该字段的最终值是无法预测的。

        /// <summary>
        /// 得到编号加1的值
        /// </summary>
        /// <returns></returns>
        public static Int64 ADD()
        {
            //新建线程，线程数加一  
            return Interlocked.Increment(ref sn);
        }
        int count = 0;

        int errorCount = 0;
        string filePath = "";

        public Form1()
        {
            InitializeComponent();
            this.listBox1.HorizontalScrollbar = true;
            textBox_block.Text = "10240";
            this.openFileDialog1.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
            this.openFileDialog1.Filter = "数据文件|*.csv";

        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                textBox1.Text = openFileDialog1.FileName;

                button2.Enabled = true;

                listBox1.Items.Clear();
                count = 0;
                errorCount = 0;
                filePath = "";
                textBox_path.Text = "";
                label_wait.Text = "尚未开始执行！";
            }
        }




        private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
        {

            ListControl ls = (ListControl)sender;

            MessageBox.Show(ls.Text);
        }

        private void button2_Click(object sender, EventArgs e)
        {

            if (!String.IsNullOrWhiteSpace(textBox1.Text))
            {

                Int64 length = 10240;//每次块大小 512000byte  500KB  这样1个G转2010次算完
                                     //10兆字节(mb)=10485760字节(b)
                                     //UTF-8编码中，一个英文字符等于一个字节，一个中文（含繁体）等于三个字节。 本次文件中主要是英文和数字，没有中文。所以要多少字就配多大块。
                                     //50千字节(kb)=51200字节(b)
                                     //200千字节(kb)=204800字节(b)
                if (String.IsNullOrWhiteSpace(textBox_block.Text) || !Int64.TryParse(textBox_block.Text, out length))
                {

                    MessageBox.Show("请正确输入分块并行读取(B)");
                    return;

                }

                label_wait.Text = "正在执行分析,请耐心等待........！";

                button1.Enabled = false;
                textBox1.Enabled = false;
                button2.Enabled = false;
                textBox_block.Enabled = false;

                listBox1.Enabled = true;
                filePath = Environment.CurrentDirectory + "\\OutPut\\" + ConvertDataTimeToLong(DateTime.Now).ToString() + ".json";



                Application.DoEvents();

                #region 执行内存映射方案

                string filepath = openFileDialog1.FileName;
                setup:
                try
                {
                    #region  并发用法：真正大文件时比StreamReader有用--820W行数据，439MB，转成字符串、split后，执行消耗7~8秒。 10K 为1块。

                    //读取文件
                    FileInfo fi = new FileInfo(filepath);
                    Int64 size = fi.Length;//byte长度


                    Int64 portion = (Int64)Math.Ceiling(size * 1.0 / length); //分成多少块

                    string fp2 = @filepath;
                    //统一使用总文件大小的内存隐射对象
                    using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fp2, FileMode.Open, "mapname", size))
                    {
                        //从零开始，每块单独并行处理。
                        Parallel.For(0, portion, (index, ParallelLoopState) =>
                          {
                              //块大小和 文件总大小-第几分区位置的剩余位置，取两者较小的
                              Int64 fileSize = Math.Min(length, size - length * index);
                              if (fileSize > 0)
                              {

                                  using (var accessor = mmf.CreateViewAccessor(length * index, fileSize, MemoryMappedFileAccess.Read))
                                  {
                                      List<string> list = new List<string>();

                                      ////每隔100是1行
                                      //for (int i = 0; i < fileSize; i += length)
                                      //{
                                      //Int64 listkey = ADD();



                                      byte[] byteArray = new byte[length];

                                      #region 函数参数说明
                                      //ReadArray参数
                                      //position
                                      //Type: System.Int64
                                      //访问器中的字节偏移量，从此处开始读取。
                                      //array
                                      //Type: T[]
                                      //包含从访问器读取的结构的数组。
                                      //offset
                                      //Type: System.Int32
                                      //array 中要将第一个复制的结构放置到的索引。
                                      //count
                                      //Type: System.Int32
                                      //要从访问器读取的 T 类型的结构的数目。

                                      //resultcount:读入 array 的结构数。 如果可用结构较少，则此值可能小于 count；如果到达访问器末尾，则为零。
                                      #endregion
                                      int resultcount = accessor.ReadArray(0, byteArray, 0, (int)fileSize);

                                      //测试通过：\r\n \r 空格 \n（注意：\r 空格 \n 都算1个字符,\r\n算2个字符）
                                      //规则：最后必须是\n结尾,cvs的换行都是\n结尾。
                                      string str = System.Text.Encoding.Default.GetString(byteArray);
                                      int index1 = str.LastIndexOf('\n');
                                      string[] k = str.Split('\n');
                                      string endstr = null;
                                      if (str.Length != index1)
                                      {
                                          //不相等，最后一行不能Split分析。
                                          //spilt时，将最后一行保存起来，本次处理索引-1。
                                          //头条保存起来，也不处理，本次处理索引-2。
                                          endstr = k[k.Count() - 1].Replace("\0", string.Empty).Replace("\r", string.Empty).Trim(); ;


                                      }
                                      //else
                                      //{
                                      //    //最后一行可以Split分析，不再处理
                                      //}

                                      //先简化开发，将头部和尾部都另外保存。只计算中间的。然后调用ES (索引-2：从1开始，尾部-1)
                                      //测试: 820W条记录，执行完毕 20~ 22秒。 最终结果3w2013条记录。有的直接点击3000多次。

                                      for (int j = 1; j < k.Count() - 1; j++)
                                      {

                                          try
                                          {
                                              string key = k[j].Split(',')[1].Replace("\0", string.Empty).Replace("\r", string.Empty).Trim();
                                              totalCountDic.AddOrUpdate(key, 1, (skey, scount) => { return scount = scount + 1; });

                                          }
                                          catch (Exception ext)
                                          {
                                              MessageBox.Show("某行数据第2列可能为空" + ext.ToString());

                                          }

                                      }

                                      //头尾：另外保存 
                                      ListModel listmodel = new ListModel(k[0].Replace("\0", string.Empty).Replace("\r", string.Empty).Trim(), endstr);
                                      listModelDic.TryAdd(index, listmodel);




                                      //}

                                  }
                                  //分区时，最后一个为0则忽略。

                              }

                          });

                    }

                    if (listModelDic.Count() > 0)
                    {
                        if (listModelDic.Count() == 1)
                        {

                            try
                            {
                                string key = listModelDic[1].End;
                                if (!String.IsNullOrWhiteSpace(key))
                                    totalCountDic.AddOrUpdate(listModelDic[1].End.Split(',')[1], 0, (skey, scount) => { return scount = scount + 1; });
                            }
                            catch (Exception ext)
                            {
                                MessageBox.Show("只有一块时（文件小）：第一行数据第2列可能为空" + ext.ToString());

                            }
                        }
                        else
                        {
                            //头尾单独链式处理：  从1开始。第0行的begin 是列名称，不需要分析。 
                            for (int i = 1; i < listModelDic.Count(); i++)
                            {
                                try
                                {
                                    //count = count + 1;//记录计算了多少次，与分块对应（其实就是与listModelDic的key-1对应即代表完整）

                                    string str = "";
                                    if (!String.IsNullOrWhiteSpace(listModelDic[i - 1].End))
                                    {
                                        //上一个的end+本轮的begin拼接，不需要去除/n
                                        str = listModelDic[i - 1].End + listModelDic[i].Begin;
                                    }
                                    else
                                    {
                                        //上一个的end（为空）+本轮的begin拼接，不需要去除/n
                                        str = listModelDic[i].Begin;

                                    }


                                    string key = str.Split(',')[1];
                                    totalCountDic.AddOrUpdate(key, 0, (skey, scount) => { return scount = scount + 1; });

                                }
                                catch (Exception ext)
                                {
                                    MessageBox.Show("头尾单独链式处理：某行数据第2列可能为空" + ext.ToString());

                                }
                            }


                            //最后补充最后一条的end 是没有换行了。所以不需要处理。肯定为null。 keyend.end 是空字符串。
                            //object keyend = listModelDic[listModelDic.Count()];

                        }
                    }
                    #endregion
                }
                catch (IOException ex)
                {
                    errorCount = errorCount + 1;
                    if (errorCount > 2)
                    {
                        MessageBox.Show(ex.ToString());
                        errorCount = 0;
                    }
                    else
                    {
                        //遇到csv无法文件无法使用时，通过修改扩展名实现。
                        filepath = Path.ChangeExtension(filepath, ".txt");
                        //个别CSV\ppt\word等文件类型，则必须强转为任何其他类型的文本后缀结构。否则MemoryMappedFile不识别.
                        goto setup;
                    }
                }
                catch (Exception ex)
                {

                    MessageBox.Show(ex.ToString());
                }

                #endregion


            }
            else
            {
                MessageBox.Show("请选择文件夹！");
            }


        

        }


        /// <summary>
        ///开发 调试 测试时使用(直接更改按钮事件)：单独测试ES的方法
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_ES_Click(object sender, EventArgs e)
        {
            //1.生成统计后ConcurrentDictionary
            button2_Click(null, null);

            try
            {
                using (FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate))
                {
                    using (StreamWriter sw = new StreamWriter(fs))
                    {
                        sw.Write("[");
                        sw.Close();
                    }
                }
                //2.循环取得item 
                foreach (var temp_item in totalCountDic)
                {
                    try
                    {
                        Item item_obj = ESItemAccess.SearchByItemID(temp_item.Key);
                        ResultItem result = new ResultItem();
                        result.ClickCount = temp_item.Value;
                        if (item_obj != null)
                        {
                            #region 能从ES中查找到值

                            result.ItemID = item_obj.ItemID;
                            result.Url = item_obj.Url;
                            result.Url = item_obj.Url;
                            result.CleanTitle = item_obj.CleanTitle;
                            result.CleanText = item_obj.CleanText;
                            result.PubDate = item_obj.PubDate == null ? "" : item_obj.PubDate.ToString("yyyy-MM-dd HH:mm:ss.fff");
                            result.MediaName = item_obj.MediaName;
                            result.DuplicationID = item_obj.DuplicationID;


                            //赋值  ResultItemAnalyzeData
                            ResultItemAnalyzeData newdata = new ResultItemAnalyzeData();
                            foreach (ItemAnalyzeData adata in item_obj.AnalyzeData)
                            {
                                if ((!String.IsNullOrWhiteSpace(adata.IssueID)) &&
                                    (adata.IssueID.Equals("Common") || adata.IssueID.Equals("Reader")))
                                {

                                    //本次有值
                                    if (null != adata.StockIDs)
                                    {
                                        //内容不需叠加
                                        if (null == newdata.StockIDs)
                                        {
                                            newdata.StockIDs = adata.StockIDs;
                                        }
                                        //else
                                        //{
                                        //    //内容需叠加 
                                        //    newdata.StockIDs = newdata.StockIDs.Union(adata.StockIDs).ToArray<string>(); //剔除重复项 

                                        //}
                                    }

                                    if (null != adata.MarketIDs)
                                    {

                                        if (null == newdata.MarketIDs)
                                        {
                                            newdata.MarketIDs = adata.MarketIDs;
                                        }
                                        //else
                                        //{

                                        //    newdata.MarketIDs = newdata.MarketIDs.Union(adata.MarketIDs).ToArray<string>(); //剔除重复项 

                                        //}
                                    }

                                    if (null != adata.BlockIDs)
                                    {
                                        //内容不需叠加
                                        if (null == newdata.BlockIDs)
                                        {
                                            newdata.BlockIDs = adata.BlockIDs;
                                        }
                                        //else
                                        //{
                                        //    //内容需叠加 
                                        //    newdata.BlockIDs = newdata.BlockIDs.Union(adata.BlockIDs).ToArray<string>(); //剔除重复项 

                                        //}
                                    }

                                    if (null != adata.IG)
                                    {
                                        if (null == newdata.IG)
                                        {
                                            newdata.IG = adata.IG;
                                        }

                                    }
                                }
                            }//end for

                            result.AnalyzeData = newdata;

                            #endregion

                        }
                        //将result结果放入到集合中。统一转为json字符串，写入文件。

                        string returnstr = JsonConvert.SerializeObject(result) + ",";


                        using (FileStream fs = new FileStream(filePath, FileMode.Append))
                        {
                            using (StreamWriter sw = new StreamWriter(fs))
                            {
                                sw.Write(returnstr);
                                sw.Flush();
                                sw.Close();
                            }
                        }
                    }
                    catch (Exception ex)
                    {

                        MessageBox.Show(ex.ToString());
                    }

                }

                using (FileStream fs = new FileStream(filePath, FileMode.Append))
                {
                    using (StreamWriter sw = new StreamWriter(fs))
                    {
                        sw.Write("]");
                        sw.Flush();
                        sw.Close();
                    }
                }
            }
            catch (Exception ext)
            {


                MessageBox.Show(ext.ToString());
            }

            #region 执行完成
            label_wait.Text = "执行完毕！可以继续选择其他文件！";
            this.textBox_path.Text = filePath;
            //listBox1.Enabled = false;
            textBox1.Enabled = true;
            button1.Enabled = true;
            textBox_block.Enabled = true;

            errorCount = 0;
            count = 0;

            #endregion
        }

        public static long ConvertDataTimeToLong(DateTime dt)
        {
            DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
            TimeSpan toNow = dt.Subtract(dtStart);
            long timeStamp = toNow.Ticks;
            //timeStamp = long.Parse(timeStamp.ToString().Substring(0, timeStamp.ToString().Length - 4));
            return timeStamp;
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }
    }
}
