using System;
using System.Data;
using System.Collections;
using System.IO;

namespace net.ivoa.data {

    public class CSVWrapper : DataWrapper {
        public const int PREVIEW_CHUNK = 1000;
        protected ArrayList rows;
        public ArrayList headers;
        public int rr, cc;
        protected class ColumnHeader { public string Name; public Type DataType;}
        /// <summary>
        /// uses a parser
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static CSVWrapper Stream2CSVWrapper(Stream s) {
            CSVLexer csvLexer = new CSVLexer(s);
            CSVParser csvParser = new CSVParser(csvLexer);
            csvParser.file();
            return csvParser.csvw;
        }

        public static IDataReader Stream2Reader(Stream s) {
            throw new NotImplementedException();
        }

        /*
         * loads schema, and sets stream position to first non-header
         */
        public static CSVWrapper Stream2Schema(StreamReader sr,ref char separator) {
            CSVWrapper rst = new CSVWrapper();
            string line = null;
            separator = ',';
            string header = null;
            rst.rr = -1;
            while (!sr.EndOfStream) {
                line = sr.ReadLine();
                rst.cc = 0;
                if (header == null) {//initialize header
                    header = line.Trim();//store for possible error processing later
                    string[] ctmp = header.Split(',');
                    string[] ttmp = header.Split('\t');
                    string[] stmp = header.Split(' ');
                    if (ctmp.Length > ttmp.Length && ctmp.Length > stmp.Length)//use ,
                        separator = ',';
                    else if (ttmp.Length > ctmp.Length && ttmp.Length > stmp.Length) //use \t
                        separator = '\t';
                    else //oh well, try space
                        separator = ' ';
                }
                else
                    rst.NewRow();
                string[] tmp = line.Split(separator);
                foreach (string st in tmp) {
                    rst.AddColumn(st);
                }
                if (header != null && rst.rr == (PREVIEW_CHUNK-1)) break;//break after first nth row, so columns are typed.
            }
            sr.BaseStream.Position = 0;
            sr.DiscardBufferedData();
            sr.ReadLine();
            return rst;
        }
        public static CSVWrapper Reader2CSVWrapper(TextReader s) {
            CSVLexer csvLexer = new CSVLexer(s);
            CSVParser csvParser = new CSVParser(csvLexer);
            csvParser.file();
            return csvParser.csvw;
        }
        public CSVWrapper() {
            rows = new ArrayList();
            headers = new ArrayList();
            rr = -2; cc = 0;
        }
        private int table;
        public int ActiveTable {
            get { return table; }
            set { table = value; }
        }
        public int TableCount { get { return 1; } }
        public int RowCount {
            get { return rows.Count; }
        }
        public int RowCap {
            set {
                throw new Exception("not implemented");
            }
        }
        public int ColCount { get { return headers.Count; } }
        public string ColName(int c) { return ((ColumnHeader)headers[c]).Name; }
        public void ColName(int c, string s) { ((ColumnHeader)headers[c]).Name = s; }
        public object ColType(int c) { return ((ColumnHeader)headers[c]).DataType; }
        public void ColType(int c, object t) { ((ColumnHeader)headers[c]).DataType = (Type)t; }
        public Type MSColType(int c) { return (Type)ColType(c); }
        public void MSColType(int c, Type t) { ColType(c, t); }
        public int AddCol(string name, Type mstype) {
            throw new Exception("not implemented");
        }
        public int NewRow() {
            ++rr;
            cc = 0;
            if (rr == -1) return -1;
            ArrayList al = new ArrayList(headers.Count);
            for (int x = 0; x < headers.Count; x++)
                al.Add(null);
            rows.Add(al);
            return rows.Count - 1;
        }
        //used by parser
        public void AddColumn(string val) {
            if (rr == -1) {
                ColumnHeader ch = new ColumnHeader();
                ch.Name = val;
                ch.DataType = null;
                headers.Add(ch);
            }
            else {
                //try {
                    if (ColType(cc) == null)
                        ColType(cc, DetermineType(val));
                    this[rr, cc] = val;
                    try {
                        switch (MSColType(cc).Name) {
                            case "Int32":
                                int.Parse(val);
                                break;
                            case "Int64":
                                long.Parse(val);
                                break;
                            case "Double":
                                double.Parse(val);
                                break;
                            case "String":
                                break;
                        }
                    }
                    catch { ColType(cc, DetermineType(val)); }
                //}
                //catch { throw new Exception("Row " + rr + " has more/less columns than the others. Please fix this line and try again."); }
            }
            ++cc;
        }
        private Type DetermineType(string val) {
            try {
                int.Parse(val);
                return typeof(Int32);
            }
            catch { }
            try {
                long.Parse(val);
                return typeof(Int64);
            }
            catch { }
            try {
                double.Parse(val);
                return typeof(double);
            }
            catch { }
            return typeof(string);
        }
        public object this[int r, int c] {
            get {
                ArrayList row = (ArrayList)rows[r];
                return row[c];
            }
            set {
                ArrayList row = (ArrayList)rows[r];
                row[c] = value;
            }
        }
        private int NewColHeader(string name, Type mstype) {
            throw new Exception("not implemented");
        }
        private int NewEmptyCol() {
            throw new Exception("not implemented");
        }
    }
}