using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Pdf417EncoderLibraryExt
{
    public static class Pdf417CommandLine
    {
        public static readonly string Help =
            "PDF417 barcode encoder console application support\r\n" +
            "AppName [optional arguments] input-file output-file\r\n" +
            "Output file must have .png extension\r\n" +
            "Options format /code:value or -code:value (the : can be =)\r\n" +
            "Encoding control. code=[encode|n], value=[auto|a|byte|b|text|t], default=a\r\n" +
            "Error correction level. code=[error|e], value=[0-8|low|l|normal|n|medium|m|high|h], default=n\r\n" +
            "Narrow bar width. code=[width|w], value=[1-100], default=2\r\n" +
            "Row height. code=[height|h], value=[3-300], default=6, min=3*width\r\n" +
            "Quiet zone. code=[quiet|q], value=[2-200], default=4, min=2*width\r\n" +
            "Layout options. Only one of data columns, data rows or image ratio\r\n" +
            "Data columns. code=[col|c], value=[1-30], default=3\r\n" +
            "Data rows. code=[row|r], value=[3-90], no default\r\n" +
            "Image ratio (width/height). code=[ratio|o], value=[decimal > 0], no default\r\n" +
            "Text file format. code=[text|t], value=iso-8859-n, see notes below\r\n" +
            "Input file is binary unless text file option is specified\r\n" +
            "If input file format is text or t with no value, character set is iso-8859-1\r\n" +
            "If input file format is text or t value must be iso-8859-n and n must be 1-9, 13, 15\r\n";

        public static void Encode
                (
                string ArgumentsLine
                )
        {
            if (ArgumentsLine.IndexOf('"') < 0)
            {
                Encode(ArgumentsLine.Split(new char[] { ' ' }));
                return;
            }
            List<string> Args = new List<string>();
            int Ptr = 0;
            int Ptr1 = 0;
            int Ptr2 = 0;
            for (; ; )
            {
                // skip white
                for (; Ptr < ArgumentsLine.Length && ArgumentsLine[Ptr] == ' '; Ptr++) ;
                if (Ptr == ArgumentsLine.Length) break;

                // test for quote
                if (ArgumentsLine[Ptr] == '"')
                {
                    // look for next quote
                    Ptr++;
                    Ptr1 = ArgumentsLine.IndexOf('"', Ptr);
                    if (Ptr1 < 0) throw new ArgumentException("Unbalanced double quote");
                    Ptr2 = Ptr1 + 1;
                }
                else
                {
                    // look for next white
                    Ptr1 = ArgumentsLine.IndexOf(' ', Ptr);
                    if (Ptr1 < 0) Ptr1 = ArgumentsLine.Length;
                    Ptr2 = Ptr1;
                }
                Args.Add(ArgumentsLine.Substring(Ptr, Ptr1 - Ptr));
                Ptr = Ptr2;
            }
            Encode(Args.ToArray());
            return;
        }

        public static void Encode
                (
                string[] Args
                )
        {
            // help
            if (Args == null || Args.Length < 2) throw new ArgumentException("help");

            int DataColumns = 0;
            int DataRows = 0;
            double Ratio = 0;
            bool TextFile = false;
            string CharacterSet = null;
            string InputFileName = null;
            string OutputFileName = null;
            string Code;
            string Value;

            Pdf417BarcodeEncoder Encoder = new Pdf417BarcodeEncoder();

            for (int ArgPtr = 0; ArgPtr < Args.Length; ArgPtr++)
            {
                string Arg = Args[ArgPtr];

                // file name
                if (Arg[0] != '/' && Arg[0] != '-')
                {
                    if (InputFileName == null)
                    {
                        InputFileName = Arg;
                        continue;
                    }
                    if (OutputFileName == null)
                    {
                        OutputFileName = Arg;
                        continue;
                    }
                    throw new ArgumentException(string.Format("Invalid option. Argument={0}", ArgPtr + 1));
                }

                // search for colon
                int Ptr = Arg.IndexOf(':');
                if (Ptr < 0) Ptr = Arg.IndexOf('=');
                if (Ptr > 0)
                {
                    Code = Arg.Substring(1, Ptr - 1);
                    Value = Arg.Substring(Ptr + 1);
                }
                else
                {
                    Code = Arg.Substring(1);
                    Value = string.Empty;
                }

                Code = Code.ToLower();
                Value = Value.ToLower();

                switch (Code)
                {
                    case "encode":
                    case "n":
                        EncodingControl EC;
                        switch (Value)
                        {
                            case "auto":
                            case "a":
                                EC = EncodingControl.Auto;
                                break;

                            case "byte":
                            case "b":
                                EC = EncodingControl.ByteOnly;
                                break;

                            case "text":
                            case "t":
                                EC = EncodingControl.TextAndByte;
                                break;

                            default:
                                throw new ArgumentException("Encoding control option in error");
                        }
                        Encoder.EncodingControl = EC;
                        break;

                    case "error":
                    case "e":
                        ErrorCorrectionLevel ECL;
                        switch (Value)
                        {
                            case "0":
                                ECL = ErrorCorrectionLevel.Level_0;
                                break;

                            case "1":
                                ECL = ErrorCorrectionLevel.Level_1;
                                break;

                            case "2":
                                ECL = ErrorCorrectionLevel.Level_2;
                                break;

                            case "3":
                                ECL = ErrorCorrectionLevel.Level_3;
                                break;

                            case "4":
                                ECL = ErrorCorrectionLevel.Level_4;
                                break;

                            case "5":
                                ECL = ErrorCorrectionLevel.Level_5;
                                break;

                            case "6":
                                ECL = ErrorCorrectionLevel.Level_6;
                                break;

                            case "7":
                                ECL = ErrorCorrectionLevel.Level_7;
                                break;

                            case "8":
                                ECL = ErrorCorrectionLevel.Level_8;
                                break;

                            case "low":
                            case "l":
                                ECL = ErrorCorrectionLevel.AutoLow;
                                break;

                            case "normal":
                            case "n":
                                ECL = ErrorCorrectionLevel.AutoNormal;
                                break;

                            case "medium":
                            case "m":
                                ECL = ErrorCorrectionLevel.AutoMedium;
                                break;

                            case "high":
                            case "h":
                                ECL = ErrorCorrectionLevel.AutoHigh;
                                break;

                            default:
                                throw new ArgumentException("Error correction level option in error");
                        }
                        Encoder.ErrorCorrection = ECL;
                        break;

                    case "width":
                    case "w":
                        if (!int.TryParse(Value, out int BarWidth)) BarWidth = -1;
                        Encoder.NarrowBarWidth = BarWidth;
                        break;

                    case "height":
                    case "h":
                        if (!int.TryParse(Value, out int RowHeight)) RowHeight = -1;
                        Encoder.RowHeight = RowHeight;
                        break;

                    case "quiet":
                    case "q":
                        if (!int.TryParse(Value, out int QuietZone)) QuietZone = -1;
                        Encoder.QuietZone = QuietZone;
                        break;

                    case "col":
                    case "c":
                        if (DataRows != 0 || Ratio != 0)
                            throw new ArgumentException("Only one value is allowed for Data Rows, Data Columns and Ratio");
                        if (!int.TryParse(Value, out DataColumns) || DataColumns < 1 || DataColumns > 30)
                            throw new ApplicationException("Data columns in error");
                        break;

                    case "row":
                    case "r":
                        if (DataColumns != 0 || Ratio != 0)
                            throw new ArgumentException("Only one value is allowed for Data Rows, Data Columns and Ratio");
                        if (!int.TryParse(Value, out DataRows) || DataRows < 3 || DataRows > 90)
                            throw new ApplicationException("Data Rows in error");
                        break;

                    case "ratio":
                    case "o":
                        if (DataRows != 0 || DataColumns != 0)
                            throw new ArgumentException("Only one value is allowed for Data Rows, Data Columns and Ratio");
                        if (!double.TryParse(Value, out Ratio) || Ratio < 0.1 || Ratio > 100)
                            throw new ApplicationException("Image aspect ratio (width/height) in error");
                        break;

                    case "text":
                    case "t":
                        TextFile = true;
                        if (Value != string.Empty) CharacterSet = Value;
                        break;

                    default:
                        throw new ApplicationException(string.Format("Invalid argument no {0}, code {1}", ArgPtr + 1, Code));
                }
            }

            if (TextFile)
            {
                string InputText = File.ReadAllText(InputFileName);
                Encoder.Encode(InputText);
            }
            else
            {
                byte[] InputBytes = File.ReadAllBytes(InputFileName);
                Encoder.Encode(InputBytes);
            }

            if (DataColumns != 0)
            {
                if (!Encoder.SetDataColumns(DataColumns))
                {
                    throw new ApplicationException("Set data columns failed");
                }
            }
            else if (DataRows != 0)
            {
                if (!Encoder.SetDataRows(DataRows))
                {
                    throw new ApplicationException("Set data rows failed");
                }
            }
            else if (Ratio != 0)
            {
                if (!Encoder.WidthToHeightRatio(Ratio))
                {
                    throw new ApplicationException("Set width to height aspect ratio failed");
                }
            }

            Encoder.SaveBarcodeToPngFile(OutputFileName);
            return;
        }
    }
}
