using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Text;

namespace net.ivoa.data {
    public class CSVStreamInfo {
        /// <summary>
        /// constructor with a temp file source (eventually)
        /// does not set col type; you must call LearnColTypes
        /// </summary>
        /// <param name="headerline"></param>
        public CSVStreamInfo(string headerline) {
            SplitToken = GuessSplitToken(headerline);
            ColumnNames = GetTokens(headerline, SplitToken);   
            CTypesI = new int[ColumnNames.Length];
            CurrentRow = new object[ColumnNames.Length];
        }
        /// <summary>
        /// Constructor from an existing table.
        /// Derives column types from database table.
        /// Do not and should not call LearnColTypes if using this.
        /// </summary>
        /// <param name="headerline"></param>
        /// <param name="cstring"></param>
        /// <param name="tablename"></param>
        public CSVStreamInfo(string headerline, string cstring, string tablename):this(headerline) {
            using (SqlConnection con = new SqlConnection(cstring)) {
                con.Open();
                using (SqlCommand com = con.CreateCommand()) {
                    com.Parameters.Add(new SqlParameter("@TABNAME", SqlDbType.VarChar, 128));
                    com.CommandText = @"
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = @TABNAME
";
                    com.Prepare();
                    com.Parameters["@TABNAME"].Value = tablename;                  
                    using (SqlDataReader sdr = com.ExecuteReader()) {
                        for (int i = 0; i < ColumnNames.Length; i++) {
                            sdr.Read();
                            string cname = sdr.GetString(0);
                            if (cname.ToLower() != ColumnNames[i].ToLower())
                                throw new Exception("Import columns do not match existing schema in '" + tablename + "'");
                            string ctype = sdr.GetString(1).ToLower();
                            int typeIndex = -1;
                            for (int j = 0; j < TypeChain.Count; j++) {
                                if (TypeChain[j].SqlType.ToString().ToLower() == ctype) {
                                    typeIndex = j;
                                    break;
                                }
                            }
                            if (typeIndex == -1) throw new Exception("Can't map type '" + ctype + "'");
                            CTypesI[i] = typeIndex;
                        }
                    }
                }
            }
        }
        public string GetSummary() {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < ColumnNames.Length; i++) {
                sb.AppendLine(ColumnNames[i].PadRight(30)+"["+TypeChain[CTypesI[i]].GetType().Name+ "]");
            }
            sb.AppendLine(RowsLearned + " rows learned with " + ColumnNames.Length + " columns");
            return sb.ToString();
        }
        public void LearnColTypes(string line) {
            string[] tokens = GetTokens(line, SplitToken);
            if (tokens.Length != ColumnNames.Length)
                throw new ColMismatchException("Expected " + ColumnNames.Length + " column[s], found " + tokens.Length);
            ++RowsLearned;
            string token;
            for (int i = 0; i < tokens.Length; i++) {
                token = tokens[i].Trim(TOKEN_TRIM_CHARS);
                CTypesI[i] = IndexOfTokenType(token, CTypesI[i]);
            }
        }
        public void ParseRow(string line) {
            string[] tokens = GetTokens(line, SplitToken);
            for (int i = 0; i < ColumnNames.Length; i++) {
                CurrentRow[i] = TypeChain[CTypesI[i]].ParseToken(tokens[i]);
            }
        }
        public string[] ColumnNames { get; private set; }
        public SqlDbType GetSqlType(int index) {
            return TypeChain[CTypesI[index]].SqlType;
        }
        public int RowsLearned{get;private set;}
        public int RowsParsed { get; private set; }
        public object[] CurrentRow { get; private set; }
        int IndexOfTokenType(string token, int fromI) {
            for (int i = fromI; i < TypeChain.Count; i++)
                if (TypeChain[i].StrIsType(token)) return i;
            throw new UnknownTypeException("Cannot determine type of '" + token + "'");
        }
        char[] GuessSplitToken(string line) {
            line = line.Trim();
            char[] rst;
            int maxLength;
            string[] cTokens = GetTokens(line, C_STOKEN);
            string[] sTokens = GetTokens(line, S_STOKEN);
            if(cTokens.Length > sTokens.Length){
                maxLength = cTokens.Length;
                rst = C_STOKEN;
            } else {
                maxLength = sTokens.Length;
                rst = S_STOKEN;
            }
            string[] tTokens = GetTokens(line, T_STOKEN);
            if (tTokens.Length > maxLength)
                rst = T_STOKEN;
            return rst;            
        }
        string[] GetTokens(string line, char[] splitToken) {
            return line.Split(splitToken, StringSplitOptions.RemoveEmptyEntries);
            //return line.Split(splitToken);
        }
        char[] SplitToken;
        int[] CTypesI;
        static readonly char[]
            C_STOKEN = new char[] { ',' },
            S_STOKEN = new char[] { ' ' },
            T_STOKEN = new char[] { '\t' }, 
            TOKEN_TRIM_CHARS = new char[]{' ','\t','"', '\''}
            ;
        static readonly List<ImpType> TypeChain = new List<ImpType>() { 
            new DateTimeType(), 
            new I1Type(), new I8Type(), new I16Type(), new I32Type(), new I64Type(),//integers
            new SmallFloatType(), new BigFloatType(),//floating points
            new StrType() //default to string
        };
    }
}
